mirror of
https://github.com/LukeHagar/better-auth.git
synced 2025-12-09 20:27:44 +00:00
refactor: move oauth2 to core (#5135)
This commit is contained in:
@@ -11,13 +11,13 @@ import { getTestInstance } from "../../test-utils/test-instance";
|
|||||||
import { parseSetCookieHeader } from "../../cookies";
|
import { parseSetCookieHeader } from "../../cookies";
|
||||||
import type { GoogleProfile } from "../../social-providers";
|
import type { GoogleProfile } from "../../social-providers";
|
||||||
import { DEFAULT_SECRET } from "../../utils/constants";
|
import { DEFAULT_SECRET } from "../../utils/constants";
|
||||||
import { getOAuth2Tokens } from "../../oauth2";
|
import { getOAuth2Tokens } from "@better-auth/core/oauth2";
|
||||||
import { signJWT } from "../../crypto/jwt";
|
import { signJWT } from "../../crypto/jwt";
|
||||||
import { BASE_ERROR_CODES } from "../../error/codes";
|
import { BASE_ERROR_CODES } from "../../error/codes";
|
||||||
import type { Account } from "../../types";
|
import type { Account } from "../../types";
|
||||||
|
|
||||||
let email = "";
|
let email = "";
|
||||||
vi.mock("../../oauth2", async (importOriginal) => {
|
vi.mock("@better-auth/core/oauth2", async (importOriginal) => {
|
||||||
const original = (await importOriginal()) as any;
|
const original = (await importOriginal()) as any;
|
||||||
return {
|
return {
|
||||||
...original,
|
...original,
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import * as z from "zod";
|
import * as z from "zod";
|
||||||
import { createAuthEndpoint } from "../call";
|
import { createAuthEndpoint } from "../call";
|
||||||
import { APIError } from "better-call";
|
import { APIError } from "better-call";
|
||||||
import { generateState, decryptOAuthToken, setTokenUtil } from "../../oauth2";
|
|
||||||
import type { OAuth2Tokens } from "@better-auth/core/oauth2";
|
import type { OAuth2Tokens } from "@better-auth/core/oauth2";
|
||||||
import {
|
import {
|
||||||
freshSessionMiddleware,
|
freshSessionMiddleware,
|
||||||
@@ -10,6 +9,8 @@ import {
|
|||||||
} from "./session";
|
} from "./session";
|
||||||
import { BASE_ERROR_CODES } from "../../error/codes";
|
import { BASE_ERROR_CODES } from "../../error/codes";
|
||||||
import { SocialProviderListEnum } from "../../social-providers";
|
import { SocialProviderListEnum } from "../../social-providers";
|
||||||
|
import { generateState } from "../../oauth2/state";
|
||||||
|
import { decryptOAuthToken, setTokenUtil } from "../../oauth2/utils";
|
||||||
|
|
||||||
export const listUserAccounts = createAuthEndpoint(
|
export const listUserAccounts = createAuthEndpoint(
|
||||||
"/list-accounts",
|
"/list-accounts",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import * as z from "zod";
|
import * as z from "zod";
|
||||||
import { setSessionCookie } from "../../cookies";
|
import { setSessionCookie } from "../../cookies";
|
||||||
import { setTokenUtil } from "../../oauth2";
|
import { setTokenUtil } from "../../oauth2/utils";
|
||||||
import { handleOAuthUserInfo } from "../../oauth2/link-account";
|
import { handleOAuthUserInfo } from "../../oauth2/link-account";
|
||||||
import { parseState } from "../../oauth2/state";
|
import { parseState } from "../../oauth2/state";
|
||||||
import { HIDE_METADATA } from "../../utils/hide-metadata";
|
import { HIDE_METADATA } from "../../utils/hide-metadata";
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
export * from "./create-authorization-url";
|
export * from "@better-auth/core/oauth2";
|
||||||
export * from "./validate-authorization-code";
|
|
||||||
export * from "./refresh-access-token";
|
|
||||||
export * from "./utils";
|
export * from "./utils";
|
||||||
export * from "./state";
|
export * from "./state";
|
||||||
export * from "./link-account";
|
export * from "./link-account";
|
||||||
|
|||||||
@@ -2,14 +2,14 @@ import { describe, expect, it, vi } from "vitest";
|
|||||||
import { getTestInstance } from "../test-utils/test-instance";
|
import { getTestInstance } from "../test-utils/test-instance";
|
||||||
import type { GoogleProfile } from "../social-providers";
|
import type { GoogleProfile } from "../social-providers";
|
||||||
import { DEFAULT_SECRET } from "../utils/constants";
|
import { DEFAULT_SECRET } from "../utils/constants";
|
||||||
import { getOAuth2Tokens } from "../oauth2";
|
import { getOAuth2Tokens } from "@better-auth/core/oauth2";
|
||||||
import { signJWT } from "../crypto/jwt";
|
import { signJWT } from "../crypto/jwt";
|
||||||
import type { User } from "../types";
|
import type { User } from "../types";
|
||||||
|
|
||||||
let mockEmail = "";
|
let mockEmail = "";
|
||||||
let mockEmailVerified = true;
|
let mockEmailVerified = true;
|
||||||
|
|
||||||
vi.mock("../oauth2", async (importOriginal) => {
|
vi.mock("@better-auth/core/oauth2", async (importOriginal) => {
|
||||||
const original = (await importOriginal()) as any;
|
const original = (await importOriginal()) as any;
|
||||||
return {
|
return {
|
||||||
...original,
|
...original,
|
||||||
|
|||||||
@@ -1,40 +1,6 @@
|
|||||||
import type { OAuth2Tokens } from "@better-auth/core/oauth2";
|
|
||||||
import { getDate } from "../utils/date";
|
|
||||||
import { createHash } from "@better-auth/utils/hash";
|
|
||||||
import { base64Url } from "@better-auth/utils/base64";
|
|
||||||
import type { AuthContext } from "../types";
|
import type { AuthContext } from "../types";
|
||||||
import { symmetricDecrypt, symmetricEncrypt } from "../crypto";
|
import { symmetricDecrypt, symmetricEncrypt } from "../crypto";
|
||||||
|
|
||||||
export async function generateCodeChallenge(codeVerifier: string) {
|
|
||||||
const codeChallengeBytes = await createHash("SHA-256").digest(codeVerifier);
|
|
||||||
return base64Url.encode(new Uint8Array(codeChallengeBytes), {
|
|
||||||
padding: false,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getOAuth2Tokens(data: Record<string, any>): OAuth2Tokens {
|
|
||||||
return {
|
|
||||||
tokenType: data.token_type,
|
|
||||||
accessToken: data.access_token,
|
|
||||||
refreshToken: data.refresh_token,
|
|
||||||
accessTokenExpiresAt: data.expires_in
|
|
||||||
? getDate(data.expires_in, "sec")
|
|
||||||
: undefined,
|
|
||||||
refreshTokenExpiresAt: data.refresh_token_expires_in
|
|
||||||
? getDate(data.refresh_token_expires_in, "sec")
|
|
||||||
: undefined,
|
|
||||||
scopes: data?.scope
|
|
||||||
? typeof data.scope === "string"
|
|
||||||
? data.scope.split(" ")
|
|
||||||
: data.scope
|
|
||||||
: [],
|
|
||||||
idToken: data.id_token,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export const encodeOAuthParameter = (value: string) =>
|
|
||||||
encodeURIComponent(value).replace(/%20/g, "+");
|
|
||||||
|
|
||||||
export function decryptOAuthToken(token: string, ctx: AuthContext) {
|
export function decryptOAuthToken(token: string, ctx: AuthContext) {
|
||||||
if (!token) return token;
|
if (!token) return token;
|
||||||
if (ctx.options.account?.encryptOAuthTokens) {
|
if (ctx.options.account?.encryptOAuthTokens) {
|
||||||
|
|||||||
@@ -8,9 +8,9 @@ import { createAuthClient } from "../../client";
|
|||||||
import type { GoogleProfile } from "../../social-providers";
|
import type { GoogleProfile } from "../../social-providers";
|
||||||
import { signJWT } from "../../crypto";
|
import { signJWT } from "../../crypto";
|
||||||
import { DEFAULT_SECRET } from "../../utils/constants";
|
import { DEFAULT_SECRET } from "../../utils/constants";
|
||||||
import { getOAuth2Tokens } from "../../oauth2";
|
import { getOAuth2Tokens } from "@better-auth/core/oauth2";
|
||||||
|
|
||||||
vi.mock("../../oauth2", async (importOriginal) => {
|
vi.mock("@better-auth/core/oauth2", async (importOriginal) => {
|
||||||
const original = (await importOriginal()) as any;
|
const original = (await importOriginal()) as any;
|
||||||
return {
|
return {
|
||||||
...original,
|
...original,
|
||||||
|
|||||||
@@ -5,10 +5,10 @@ import { createAuthClient } from "../../client";
|
|||||||
import { anonymousClient } from "./client";
|
import { anonymousClient } from "./client";
|
||||||
import type { GoogleProfile } from "../../social-providers";
|
import type { GoogleProfile } from "../../social-providers";
|
||||||
import { DEFAULT_SECRET } from "../../utils/constants";
|
import { DEFAULT_SECRET } from "../../utils/constants";
|
||||||
import { getOAuth2Tokens } from "../../oauth2";
|
import { getOAuth2Tokens } from "@better-auth/core/oauth2";
|
||||||
import { signJWT } from "../../crypto/jwt";
|
import { signJWT } from "../../crypto/jwt";
|
||||||
|
|
||||||
vi.mock("../../oauth2", async (importOriginal) => {
|
vi.mock("@better-auth/core/oauth2", async (importOriginal) => {
|
||||||
const original = (await importOriginal()) as any;
|
const original = (await importOriginal()) as any;
|
||||||
return {
|
return {
|
||||||
...original,
|
...original,
|
||||||
|
|||||||
@@ -8,14 +8,14 @@ import { BASE_ERROR_CODES } from "../../error/codes";
|
|||||||
import {
|
import {
|
||||||
createAuthorizationURL,
|
createAuthorizationURL,
|
||||||
validateAuthorizationCode,
|
validateAuthorizationCode,
|
||||||
} from "../../oauth2";
|
} from "@better-auth/core/oauth2";
|
||||||
import type {
|
import type {
|
||||||
OAuth2Tokens,
|
OAuth2Tokens,
|
||||||
OAuth2UserInfo,
|
OAuth2UserInfo,
|
||||||
OAuthProvider,
|
OAuthProvider,
|
||||||
} from "@better-auth/core/oauth2";
|
} from "@better-auth/core/oauth2";
|
||||||
import { handleOAuthUserInfo } from "../../oauth2/link-account";
|
import { handleOAuthUserInfo } from "../../oauth2/link-account";
|
||||||
import { refreshAccessToken } from "../../oauth2/refresh-access-token";
|
import { refreshAccessToken } from "@better-auth/core/oauth2";
|
||||||
import { generateState, parseState } from "../../oauth2/state";
|
import { generateState, parseState } from "../../oauth2/state";
|
||||||
import type {
|
import type {
|
||||||
BetterAuthPlugin,
|
BetterAuthPlugin,
|
||||||
|
|||||||
@@ -5,11 +5,11 @@ import { lastLoginMethodClient } from "./client";
|
|||||||
import { parseCookies, parseSetCookieHeader } from "../../cookies";
|
import { parseCookies, parseSetCookieHeader } from "../../cookies";
|
||||||
import { DEFAULT_SECRET } from "../../utils/constants";
|
import { DEFAULT_SECRET } from "../../utils/constants";
|
||||||
import type { GoogleProfile } from "../../social-providers/google";
|
import type { GoogleProfile } from "../../social-providers/google";
|
||||||
import { getOAuth2Tokens } from "../../oauth2";
|
import { getOAuth2Tokens } from "@better-auth/core/oauth2";
|
||||||
import { signJWT } from "../../crypto/jwt";
|
import { signJWT } from "../../crypto/jwt";
|
||||||
|
|
||||||
// Mock OAuth2 functions to return valid tokens for testing
|
// Mock OAuth2 functions to return valid tokens for testing
|
||||||
vi.mock("../../oauth2", async (importOriginal) => {
|
vi.mock("@better-auth/core/oauth2", async (importOriginal) => {
|
||||||
const original = (await importOriginal()) as any;
|
const original = (await importOriginal()) as any;
|
||||||
return {
|
return {
|
||||||
...original,
|
...original,
|
||||||
|
|||||||
@@ -3,10 +3,10 @@ import { getTestInstance } from "../../test-utils/test-instance";
|
|||||||
import { oAuthProxy } from ".";
|
import { oAuthProxy } from ".";
|
||||||
import type { GoogleProfile } from "../../social-providers";
|
import type { GoogleProfile } from "../../social-providers";
|
||||||
import { DEFAULT_SECRET } from "../../utils/constants";
|
import { DEFAULT_SECRET } from "../../utils/constants";
|
||||||
import { getOAuth2Tokens } from "../../oauth2";
|
import { getOAuth2Tokens } from "@better-auth/core/oauth2";
|
||||||
import { signJWT } from "../../crypto/jwt";
|
import { signJWT } from "../../crypto/jwt";
|
||||||
|
|
||||||
vi.mock("../../oauth2", async (importOriginal) => {
|
vi.mock("@better-auth/core/oauth2", async (importOriginal) => {
|
||||||
const original = (await importOriginal()) as any;
|
const original = (await importOriginal()) as any;
|
||||||
return {
|
return {
|
||||||
...original,
|
...original,
|
||||||
|
|||||||
@@ -3,17 +3,16 @@ import { APIError, createAuthEndpoint, sessionMiddleware } from "../../api";
|
|||||||
import type { BetterAuthPlugin, User } from "../../types";
|
import type { BetterAuthPlugin, User } from "../../types";
|
||||||
import {
|
import {
|
||||||
createAuthorizationURL,
|
createAuthorizationURL,
|
||||||
generateState,
|
|
||||||
parseState,
|
|
||||||
setTokenUtil,
|
|
||||||
validateAuthorizationCode,
|
validateAuthorizationCode,
|
||||||
validateToken,
|
validateToken,
|
||||||
} from "../../oauth2";
|
} from "@better-auth/core/oauth2";
|
||||||
import { betterFetch, BetterFetchError } from "@better-fetch/fetch";
|
import { betterFetch, BetterFetchError } from "@better-fetch/fetch";
|
||||||
import { decodeJwt } from "jose";
|
import { decodeJwt } from "jose";
|
||||||
import { handleOAuthUserInfo } from "../../oauth2/link-account";
|
import { handleOAuthUserInfo } from "../../oauth2/link-account";
|
||||||
import { setSessionCookie } from "../../cookies";
|
import { setSessionCookie } from "../../cookies";
|
||||||
import type { OAuth2Tokens } from "@better-auth/core/oauth2";
|
import type { OAuth2Tokens } from "@better-auth/core/oauth2";
|
||||||
|
import { generateState, parseState } from "../../oauth2/state";
|
||||||
|
import { setTokenUtil } from "../../oauth2/utils";
|
||||||
|
|
||||||
export interface SSOOptions {
|
export interface SSOOptions {
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import {
|
|||||||
refreshAccessToken,
|
refreshAccessToken,
|
||||||
createAuthorizationURL,
|
createAuthorizationURL,
|
||||||
validateAuthorizationCode,
|
validateAuthorizationCode,
|
||||||
} from "../oauth2";
|
} from "@better-auth/core/oauth2";
|
||||||
export interface AppleProfile {
|
export interface AppleProfile {
|
||||||
/**
|
/**
|
||||||
* The subject registered claim identifies the principal that’s the subject
|
* The subject registered claim identifies the principal that’s the subject
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
import { betterFetch } from "@better-fetch/fetch";
|
import { betterFetch } from "@better-fetch/fetch";
|
||||||
import { BetterAuthError } from "../error";
|
import { BetterAuthError } from "../error";
|
||||||
import type { OAuthProvider, ProviderOptions } from "@better-auth/core/oauth2";
|
import type { OAuthProvider, ProviderOptions } from "@better-auth/core/oauth2";
|
||||||
import { createAuthorizationURL, validateAuthorizationCode } from "../oauth2";
|
import {
|
||||||
|
createAuthorizationURL,
|
||||||
|
validateAuthorizationCode,
|
||||||
|
} from "@better-auth/core/oauth2";
|
||||||
import { logger } from "@better-auth/core/env";
|
import { logger } from "@better-auth/core/env";
|
||||||
import { refreshAccessToken } from "../oauth2/refresh-access-token";
|
import { refreshAccessToken } from "@better-auth/core/oauth2";
|
||||||
|
|
||||||
export interface AtlassianProfile {
|
export interface AtlassianProfile {
|
||||||
account_type?: string;
|
account_type?: string;
|
||||||
|
|||||||
@@ -2,9 +2,12 @@ import { betterFetch } from "@better-fetch/fetch";
|
|||||||
import { decodeJwt, decodeProtectedHeader, importJWK, jwtVerify } from "jose";
|
import { decodeJwt, decodeProtectedHeader, importJWK, jwtVerify } from "jose";
|
||||||
import { BetterAuthError } from "../error";
|
import { BetterAuthError } from "../error";
|
||||||
import type { OAuthProvider, ProviderOptions } from "@better-auth/core/oauth2";
|
import type { OAuthProvider, ProviderOptions } from "@better-auth/core/oauth2";
|
||||||
import { createAuthorizationURL, validateAuthorizationCode } from "../oauth2";
|
import {
|
||||||
|
createAuthorizationURL,
|
||||||
|
validateAuthorizationCode,
|
||||||
|
} from "@better-auth/core/oauth2";
|
||||||
import { logger } from "@better-auth/core/env";
|
import { logger } from "@better-auth/core/env";
|
||||||
import { refreshAccessToken } from "../oauth2/refresh-access-token";
|
import { refreshAccessToken } from "@better-auth/core/oauth2";
|
||||||
import { APIError } from "better-call";
|
import { APIError } from "better-call";
|
||||||
|
|
||||||
export interface CognitoProfile {
|
export interface CognitoProfile {
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
import { betterFetch } from "@better-fetch/fetch";
|
import { betterFetch } from "@better-fetch/fetch";
|
||||||
import type { OAuthProvider, ProviderOptions } from "@better-auth/core/oauth2";
|
import type { OAuthProvider, ProviderOptions } from "@better-auth/core/oauth2";
|
||||||
import { refreshAccessToken, validateAuthorizationCode } from "../oauth2";
|
import {
|
||||||
|
refreshAccessToken,
|
||||||
|
validateAuthorizationCode,
|
||||||
|
} from "@better-auth/core/oauth2";
|
||||||
export interface DiscordProfile extends Record<string, any> {
|
export interface DiscordProfile extends Record<string, any> {
|
||||||
/** the user's id (i.e. the numerical snowflake) */
|
/** the user's id (i.e. the numerical snowflake) */
|
||||||
id: string;
|
id: string;
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import {
|
|||||||
createAuthorizationURL,
|
createAuthorizationURL,
|
||||||
refreshAccessToken,
|
refreshAccessToken,
|
||||||
validateAuthorizationCode,
|
validateAuthorizationCode,
|
||||||
} from "../oauth2";
|
} from "@better-auth/core/oauth2";
|
||||||
|
|
||||||
export interface DropboxProfile {
|
export interface DropboxProfile {
|
||||||
account_id: string;
|
account_id: string;
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
import { betterFetch } from "@better-fetch/fetch";
|
import { betterFetch } from "@better-fetch/fetch";
|
||||||
import type { OAuthProvider, ProviderOptions } from "@better-auth/core/oauth2";
|
import type { OAuthProvider, ProviderOptions } from "@better-auth/core/oauth2";
|
||||||
import { createAuthorizationURL, validateAuthorizationCode } from "../oauth2";
|
import {
|
||||||
|
createAuthorizationURL,
|
||||||
|
validateAuthorizationCode,
|
||||||
|
} from "@better-auth/core/oauth2";
|
||||||
import { createRemoteJWKSet, jwtVerify, decodeJwt } from "jose";
|
import { createRemoteJWKSet, jwtVerify, decodeJwt } from "jose";
|
||||||
import { refreshAccessToken } from "../oauth2";
|
import { refreshAccessToken } from "@better-auth/core/oauth2";
|
||||||
export interface FacebookProfile {
|
export interface FacebookProfile {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
import { betterFetch } from "@better-fetch/fetch";
|
import { betterFetch } from "@better-fetch/fetch";
|
||||||
import { BetterAuthError } from "../error";
|
import { BetterAuthError } from "../error";
|
||||||
import type { OAuthProvider, ProviderOptions } from "@better-auth/core/oauth2";
|
import type { OAuthProvider, ProviderOptions } from "@better-auth/core/oauth2";
|
||||||
import { createAuthorizationURL, validateAuthorizationCode } from "../oauth2";
|
import {
|
||||||
|
createAuthorizationURL,
|
||||||
|
validateAuthorizationCode,
|
||||||
|
} from "@better-auth/core/oauth2";
|
||||||
import { logger } from "@better-auth/core/env";
|
import { logger } from "@better-auth/core/env";
|
||||||
import { refreshAccessToken } from "../oauth2/refresh-access-token";
|
import { refreshAccessToken } from "@better-auth/core/oauth2";
|
||||||
|
|
||||||
export interface FigmaProfile {
|
export interface FigmaProfile {
|
||||||
id: string;
|
id: string;
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import {
|
|||||||
createAuthorizationURL,
|
createAuthorizationURL,
|
||||||
refreshAccessToken,
|
refreshAccessToken,
|
||||||
validateAuthorizationCode,
|
validateAuthorizationCode,
|
||||||
} from "../oauth2";
|
} from "@better-auth/core/oauth2";
|
||||||
|
|
||||||
export interface GithubProfile {
|
export interface GithubProfile {
|
||||||
login: string;
|
login: string;
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import {
|
|||||||
createAuthorizationURL,
|
createAuthorizationURL,
|
||||||
validateAuthorizationCode,
|
validateAuthorizationCode,
|
||||||
refreshAccessToken,
|
refreshAccessToken,
|
||||||
} from "../oauth2";
|
} from "@better-auth/core/oauth2";
|
||||||
|
|
||||||
export interface GitlabProfile extends Record<string, any> {
|
export interface GitlabProfile extends Record<string, any> {
|
||||||
id: number;
|
id: number;
|
||||||
|
|||||||
@@ -2,9 +2,12 @@ import { betterFetch } from "@better-fetch/fetch";
|
|||||||
import { decodeJwt } from "jose";
|
import { decodeJwt } from "jose";
|
||||||
import { BetterAuthError } from "../error";
|
import { BetterAuthError } from "../error";
|
||||||
import type { OAuthProvider, ProviderOptions } from "@better-auth/core/oauth2";
|
import type { OAuthProvider, ProviderOptions } from "@better-auth/core/oauth2";
|
||||||
import { createAuthorizationURL, validateAuthorizationCode } from "../oauth2";
|
import {
|
||||||
|
createAuthorizationURL,
|
||||||
|
validateAuthorizationCode,
|
||||||
|
} from "@better-auth/core/oauth2";
|
||||||
import { logger } from "@better-auth/core/env";
|
import { logger } from "@better-auth/core/env";
|
||||||
import { refreshAccessToken } from "../oauth2/refresh-access-token";
|
import { refreshAccessToken } from "@better-auth/core/oauth2";
|
||||||
|
|
||||||
export interface GoogleProfile {
|
export interface GoogleProfile {
|
||||||
aud: string;
|
aud: string;
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import {
|
|||||||
createAuthorizationURL,
|
createAuthorizationURL,
|
||||||
validateAuthorizationCode,
|
validateAuthorizationCode,
|
||||||
refreshAccessToken,
|
refreshAccessToken,
|
||||||
} from "../oauth2";
|
} from "@better-auth/core/oauth2";
|
||||||
|
|
||||||
export interface HuggingFaceProfile {
|
export interface HuggingFaceProfile {
|
||||||
sub: string;
|
sub: string;
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import {
|
|||||||
createAuthorizationURL,
|
createAuthorizationURL,
|
||||||
validateAuthorizationCode,
|
validateAuthorizationCode,
|
||||||
refreshAccessToken,
|
refreshAccessToken,
|
||||||
} from "../oauth2";
|
} from "@better-auth/core/oauth2";
|
||||||
|
|
||||||
interface Partner {
|
interface Partner {
|
||||||
/** Partner-specific ID (consent required: kakaotalk_message) */
|
/** Partner-specific ID (consent required: kakaotalk_message) */
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
import { betterFetch } from "@better-fetch/fetch";
|
import { betterFetch } from "@better-fetch/fetch";
|
||||||
import { createAuthorizationURL, validateAuthorizationCode } from "../oauth2";
|
import {
|
||||||
|
createAuthorizationURL,
|
||||||
|
validateAuthorizationCode,
|
||||||
|
} from "@better-auth/core/oauth2";
|
||||||
import type { OAuthProvider, ProviderOptions } from "@better-auth/core/oauth2";
|
import type { OAuthProvider, ProviderOptions } from "@better-auth/core/oauth2";
|
||||||
|
|
||||||
export interface KickProfile {
|
export interface KickProfile {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import {
|
|||||||
createAuthorizationURL,
|
createAuthorizationURL,
|
||||||
refreshAccessToken,
|
refreshAccessToken,
|
||||||
validateAuthorizationCode,
|
validateAuthorizationCode,
|
||||||
} from "../oauth2";
|
} from "@better-auth/core/oauth2";
|
||||||
|
|
||||||
export interface LineIdTokenPayload {
|
export interface LineIdTokenPayload {
|
||||||
iss: string;
|
iss: string;
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import {
|
|||||||
createAuthorizationURL,
|
createAuthorizationURL,
|
||||||
refreshAccessToken,
|
refreshAccessToken,
|
||||||
validateAuthorizationCode,
|
validateAuthorizationCode,
|
||||||
} from "../oauth2";
|
} from "@better-auth/core/oauth2";
|
||||||
|
|
||||||
interface LinearUser {
|
interface LinearUser {
|
||||||
id: string;
|
id: string;
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import {
|
|||||||
createAuthorizationURL,
|
createAuthorizationURL,
|
||||||
validateAuthorizationCode,
|
validateAuthorizationCode,
|
||||||
refreshAccessToken,
|
refreshAccessToken,
|
||||||
} from "../oauth2";
|
} from "@better-auth/core/oauth2";
|
||||||
|
|
||||||
export interface LinkedInProfile {
|
export interface LinkedInProfile {
|
||||||
sub: string;
|
sub: string;
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import {
|
|||||||
validateAuthorizationCode,
|
validateAuthorizationCode,
|
||||||
createAuthorizationURL,
|
createAuthorizationURL,
|
||||||
refreshAccessToken,
|
refreshAccessToken,
|
||||||
} from "../oauth2";
|
} from "@better-auth/core/oauth2";
|
||||||
import type { OAuthProvider, ProviderOptions } from "@better-auth/core/oauth2";
|
import type { OAuthProvider, ProviderOptions } from "@better-auth/core/oauth2";
|
||||||
import { betterFetch } from "@better-fetch/fetch";
|
import { betterFetch } from "@better-fetch/fetch";
|
||||||
import { logger } from "@better-auth/core/env";
|
import { logger } from "@better-auth/core/env";
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import {
|
|||||||
createAuthorizationURL,
|
createAuthorizationURL,
|
||||||
validateAuthorizationCode,
|
validateAuthorizationCode,
|
||||||
refreshAccessToken,
|
refreshAccessToken,
|
||||||
} from "../oauth2";
|
} from "@better-auth/core/oauth2";
|
||||||
|
|
||||||
export interface NaverProfile {
|
export interface NaverProfile {
|
||||||
/** API response result code */
|
/** API response result code */
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import {
|
|||||||
createAuthorizationURL,
|
createAuthorizationURL,
|
||||||
refreshAccessToken,
|
refreshAccessToken,
|
||||||
validateAuthorizationCode,
|
validateAuthorizationCode,
|
||||||
} from "../oauth2";
|
} from "@better-auth/core/oauth2";
|
||||||
|
|
||||||
export interface NotionProfile {
|
export interface NotionProfile {
|
||||||
object: "user";
|
object: "user";
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { betterFetch } from "@better-fetch/fetch";
|
import { betterFetch } from "@better-fetch/fetch";
|
||||||
import { BetterAuthError } from "../error";
|
import { BetterAuthError } from "../error";
|
||||||
import type { OAuthProvider, ProviderOptions } from "@better-auth/core/oauth2";
|
import type { OAuthProvider, ProviderOptions } from "@better-auth/core/oauth2";
|
||||||
import { createAuthorizationURL } from "../oauth2";
|
import { createAuthorizationURL } from "@better-auth/core/oauth2";
|
||||||
import { logger } from "@better-auth/core/env";
|
import { logger } from "@better-auth/core/env";
|
||||||
import { decodeJwt } from "jose";
|
import { decodeJwt } from "jose";
|
||||||
import { base64 } from "@better-auth/utils/base64";
|
import { base64 } from "@better-auth/utils/base64";
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import {
|
|||||||
createAuthorizationURL,
|
createAuthorizationURL,
|
||||||
getOAuth2Tokens,
|
getOAuth2Tokens,
|
||||||
refreshAccessToken,
|
refreshAccessToken,
|
||||||
} from "../oauth2";
|
} from "@better-auth/core/oauth2";
|
||||||
import { base64 } from "@better-auth/utils/base64";
|
import { base64 } from "@better-auth/utils/base64";
|
||||||
|
|
||||||
export interface RedditProfile {
|
export interface RedditProfile {
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
import { betterFetch } from "@better-fetch/fetch";
|
import { betterFetch } from "@better-fetch/fetch";
|
||||||
import type { OAuthProvider, ProviderOptions } from "@better-auth/core/oauth2";
|
import type { OAuthProvider, ProviderOptions } from "@better-auth/core/oauth2";
|
||||||
import { validateAuthorizationCode, refreshAccessToken } from "../oauth2";
|
import {
|
||||||
|
validateAuthorizationCode,
|
||||||
|
refreshAccessToken,
|
||||||
|
} from "@better-auth/core/oauth2";
|
||||||
|
|
||||||
export interface RobloxProfile extends Record<string, any> {
|
export interface RobloxProfile extends Record<string, any> {
|
||||||
/** the user's id */
|
/** the user's id */
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
import { betterFetch } from "@better-fetch/fetch";
|
import { betterFetch } from "@better-fetch/fetch";
|
||||||
import { BetterAuthError } from "../error";
|
import { BetterAuthError } from "../error";
|
||||||
import type { OAuthProvider, ProviderOptions } from "@better-auth/core/oauth2";
|
import type { OAuthProvider, ProviderOptions } from "@better-auth/core/oauth2";
|
||||||
import { createAuthorizationURL, validateAuthorizationCode } from "../oauth2";
|
import {
|
||||||
|
createAuthorizationURL,
|
||||||
|
validateAuthorizationCode,
|
||||||
|
} from "@better-auth/core/oauth2";
|
||||||
import { logger } from "@better-auth/core/env";
|
import { logger } from "@better-auth/core/env";
|
||||||
import { refreshAccessToken } from "../oauth2/refresh-access-token";
|
import { refreshAccessToken } from "@better-auth/core/oauth2";
|
||||||
|
|
||||||
export interface SalesforceProfile {
|
export interface SalesforceProfile {
|
||||||
sub: string;
|
sub: string;
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
import { betterFetch } from "@better-fetch/fetch";
|
import { betterFetch } from "@better-fetch/fetch";
|
||||||
import type { OAuthProvider, ProviderOptions } from "@better-auth/core/oauth2";
|
import type { OAuthProvider, ProviderOptions } from "@better-auth/core/oauth2";
|
||||||
import { validateAuthorizationCode, refreshAccessToken } from "../oauth2";
|
import {
|
||||||
|
validateAuthorizationCode,
|
||||||
|
refreshAccessToken,
|
||||||
|
} from "@better-auth/core/oauth2";
|
||||||
|
|
||||||
export interface SlackProfile extends Record<string, any> {
|
export interface SlackProfile extends Record<string, any> {
|
||||||
ok: boolean;
|
ok: boolean;
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { getTestInstance } from "../test-utils/test-instance";
|
|||||||
import { DEFAULT_SECRET } from "../utils/constants";
|
import { DEFAULT_SECRET } from "../utils/constants";
|
||||||
import type { GoogleProfile } from "./google";
|
import type { GoogleProfile } from "./google";
|
||||||
import { parseSetCookieHeader } from "../cookies";
|
import { parseSetCookieHeader } from "../cookies";
|
||||||
import { getOAuth2Tokens, refreshAccessToken } from "../oauth2";
|
import { getOAuth2Tokens, refreshAccessToken } from "@better-auth/core/oauth2";
|
||||||
import { signJWT } from "../crypto/jwt";
|
import { signJWT } from "../crypto/jwt";
|
||||||
import { OAuth2Server } from "oauth2-mock-server";
|
import { OAuth2Server } from "oauth2-mock-server";
|
||||||
import { betterFetch } from "@better-fetch/fetch";
|
import { betterFetch } from "@better-fetch/fetch";
|
||||||
@@ -13,7 +13,7 @@ import { getMigrations } from "../db";
|
|||||||
let server = new OAuth2Server();
|
let server = new OAuth2Server();
|
||||||
let port = 8005;
|
let port = 8005;
|
||||||
|
|
||||||
vi.mock("../oauth2", async (importOriginal) => {
|
vi.mock("@better-auth/core/oauth2", async (importOriginal) => {
|
||||||
const original = (await importOriginal()) as any;
|
const original = (await importOriginal()) as any;
|
||||||
return {
|
return {
|
||||||
...original,
|
...original,
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import {
|
|||||||
createAuthorizationURL,
|
createAuthorizationURL,
|
||||||
validateAuthorizationCode,
|
validateAuthorizationCode,
|
||||||
refreshAccessToken,
|
refreshAccessToken,
|
||||||
} from "../oauth2";
|
} from "@better-auth/core/oauth2";
|
||||||
|
|
||||||
export interface SpotifyProfile {
|
export interface SpotifyProfile {
|
||||||
id: string;
|
id: string;
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
import { betterFetch } from "@better-fetch/fetch";
|
import { betterFetch } from "@better-fetch/fetch";
|
||||||
import type { OAuthProvider, ProviderOptions } from "@better-auth/core/oauth2";
|
import type { OAuthProvider, ProviderOptions } from "@better-auth/core/oauth2";
|
||||||
import { refreshAccessToken, validateAuthorizationCode } from "../oauth2";
|
import {
|
||||||
|
refreshAccessToken,
|
||||||
|
validateAuthorizationCode,
|
||||||
|
} from "@better-auth/core/oauth2";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* [More info](https://developers.tiktok.com/doc/tiktok-api-v2-get-user-info/)
|
* [More info](https://developers.tiktok.com/doc/tiktok-api-v2-get-user-info/)
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import {
|
|||||||
createAuthorizationURL,
|
createAuthorizationURL,
|
||||||
validateAuthorizationCode,
|
validateAuthorizationCode,
|
||||||
refreshAccessToken,
|
refreshAccessToken,
|
||||||
} from "../oauth2";
|
} from "@better-auth/core/oauth2";
|
||||||
import { decodeJwt } from "jose";
|
import { decodeJwt } from "jose";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import {
|
|||||||
createAuthorizationURL,
|
createAuthorizationURL,
|
||||||
refreshAccessToken,
|
refreshAccessToken,
|
||||||
validateAuthorizationCode,
|
validateAuthorizationCode,
|
||||||
} from "../oauth2";
|
} from "@better-auth/core/oauth2";
|
||||||
|
|
||||||
export interface TwitterProfile {
|
export interface TwitterProfile {
|
||||||
data: {
|
data: {
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import {
|
|||||||
createAuthorizationURL,
|
createAuthorizationURL,
|
||||||
validateAuthorizationCode,
|
validateAuthorizationCode,
|
||||||
refreshAccessToken,
|
refreshAccessToken,
|
||||||
} from "../oauth2";
|
} from "@better-auth/core/oauth2";
|
||||||
|
|
||||||
export interface VkProfile {
|
export interface VkProfile {
|
||||||
user: {
|
user: {
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
import { betterFetch } from "@better-fetch/fetch";
|
import { betterFetch } from "@better-fetch/fetch";
|
||||||
import { generateCodeChallenge, validateAuthorizationCode } from "../oauth2";
|
import {
|
||||||
|
generateCodeChallenge,
|
||||||
|
validateAuthorizationCode,
|
||||||
|
} from "@better-auth/core/oauth2";
|
||||||
import type { OAuthProvider, ProviderOptions } from "@better-auth/core/oauth2";
|
import type { OAuthProvider, ProviderOptions } from "@better-auth/core/oauth2";
|
||||||
|
|
||||||
export type LoginType =
|
export type LoginType =
|
||||||
|
|||||||
@@ -86,10 +86,18 @@
|
|||||||
"typecheck": "tsc --project tsconfig.json"
|
"typecheck": "tsc --project tsconfig.json"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@better-auth/utils": "0.3.0",
|
||||||
|
"@better-fetch/fetch": "catalog:",
|
||||||
|
"jose": "^6.1.0",
|
||||||
"unbuild": "catalog:"
|
"unbuild": "catalog:"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"better-call": "catalog:",
|
"better-call": "catalog:",
|
||||||
"zod": "^4.1.5"
|
"zod": "^4.1.5"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@better-auth/utils": "0.3.0",
|
||||||
|
"@better-fetch/fetch": "catalog:",
|
||||||
|
"jose": "^6.1.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { betterFetch } from "@better-fetch/fetch";
|
import { betterFetch } from "@better-fetch/fetch";
|
||||||
import { base64Url } from "@better-auth/utils/base64";
|
import { base64Url } from "@better-auth/utils/base64";
|
||||||
import type { OAuth2Tokens, ProviderOptions } from "@better-auth/core/oauth2";
|
import type { OAuth2Tokens, ProviderOptions } from "./oauth-provider";
|
||||||
|
|
||||||
export function createClientCredentialsTokenRequest({
|
export function createClientCredentialsTokenRequest({
|
||||||
options,
|
options,
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { ProviderOptions } from "@better-auth/core/oauth2";
|
import type { ProviderOptions } from "./index";
|
||||||
import { generateCodeChallenge } from "./utils";
|
import { generateCodeChallenge } from "./utils";
|
||||||
|
|
||||||
export async function createAuthorizationURL({
|
export async function createAuthorizationURL({
|
||||||
@@ -1,194 +1,22 @@
|
|||||||
import type { LiteralString } from "../types/helper";
|
export type {
|
||||||
|
OAuth2Tokens,
|
||||||
|
OAuthProvider,
|
||||||
|
OAuth2UserInfo,
|
||||||
|
ProviderOptions,
|
||||||
|
} from "./oauth-provider";
|
||||||
|
|
||||||
export interface OAuth2Tokens {
|
export { generateCodeChallenge, getOAuth2Tokens } from "./utils";
|
||||||
tokenType?: string;
|
export { createAuthorizationURL } from "./create-authorization-url";
|
||||||
accessToken?: string;
|
export {
|
||||||
refreshToken?: string;
|
createAuthorizationCodeRequest,
|
||||||
accessTokenExpiresAt?: Date;
|
validateAuthorizationCode,
|
||||||
refreshTokenExpiresAt?: Date;
|
validateToken,
|
||||||
scopes?: string[];
|
} from "./validate-authorization-code";
|
||||||
idToken?: string;
|
export {
|
||||||
}
|
createRefreshAccessTokenRequest,
|
||||||
|
refreshAccessToken,
|
||||||
export type OAuth2UserInfo = {
|
} from "./refresh-access-token";
|
||||||
id: string | number;
|
export {
|
||||||
name?: string;
|
clientCredentialsToken,
|
||||||
email?: string | null;
|
createClientCredentialsTokenRequest,
|
||||||
image?: string;
|
} from "./client-credentials-token";
|
||||||
emailVerified: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
export interface OAuthProvider<
|
|
||||||
T extends Record<string, any> = Record<string, any>,
|
|
||||||
O extends Record<string, any> = Partial<ProviderOptions>,
|
|
||||||
> {
|
|
||||||
id: LiteralString;
|
|
||||||
createAuthorizationURL: (data: {
|
|
||||||
state: string;
|
|
||||||
codeVerifier: string;
|
|
||||||
scopes?: string[];
|
|
||||||
redirectURI: string;
|
|
||||||
display?: string;
|
|
||||||
loginHint?: string;
|
|
||||||
}) => Promise<URL> | URL;
|
|
||||||
name: string;
|
|
||||||
validateAuthorizationCode: (data: {
|
|
||||||
code: string;
|
|
||||||
redirectURI: string;
|
|
||||||
codeVerifier?: string;
|
|
||||||
deviceId?: string;
|
|
||||||
}) => Promise<OAuth2Tokens>;
|
|
||||||
getUserInfo: (
|
|
||||||
token: OAuth2Tokens & {
|
|
||||||
/**
|
|
||||||
* The user object from the provider
|
|
||||||
* This is only available for some providers like Apple
|
|
||||||
*/
|
|
||||||
user?: {
|
|
||||||
name?: {
|
|
||||||
firstName?: string;
|
|
||||||
lastName?: string;
|
|
||||||
};
|
|
||||||
email?: string;
|
|
||||||
};
|
|
||||||
},
|
|
||||||
) => Promise<{
|
|
||||||
user: OAuth2UserInfo;
|
|
||||||
data: T;
|
|
||||||
} | null>;
|
|
||||||
/**
|
|
||||||
* Custom function to refresh a token
|
|
||||||
*/
|
|
||||||
refreshAccessToken?: (refreshToken: string) => Promise<OAuth2Tokens>;
|
|
||||||
revokeToken?: (token: string) => Promise<void>;
|
|
||||||
/**
|
|
||||||
* Verify the id token
|
|
||||||
* @param token - The id token
|
|
||||||
* @param nonce - The nonce
|
|
||||||
* @returns True if the id token is valid, false otherwise
|
|
||||||
*/
|
|
||||||
verifyIdToken?: (token: string, nonce?: string) => Promise<boolean>;
|
|
||||||
/**
|
|
||||||
* Disable implicit sign up for new users. When set to true for the provider,
|
|
||||||
* sign-in need to be called with with requestSignUp as true to create new users.
|
|
||||||
*/
|
|
||||||
disableImplicitSignUp?: boolean;
|
|
||||||
/**
|
|
||||||
* Disable sign up for new users.
|
|
||||||
*/
|
|
||||||
disableSignUp?: boolean;
|
|
||||||
/**
|
|
||||||
* Options for the provider
|
|
||||||
*/
|
|
||||||
options?: O;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ProviderOptions<Profile extends Record<string, any> = any> = {
|
|
||||||
/**
|
|
||||||
* The client ID of your application.
|
|
||||||
*
|
|
||||||
* This is usually a string but can be any type depending on the provider.
|
|
||||||
*/
|
|
||||||
clientId?: unknown;
|
|
||||||
/**
|
|
||||||
* The client secret of your application
|
|
||||||
*/
|
|
||||||
clientSecret?: string;
|
|
||||||
/**
|
|
||||||
* The scopes you want to request from the provider
|
|
||||||
*/
|
|
||||||
scope?: string[];
|
|
||||||
/**
|
|
||||||
* Remove default scopes of the provider
|
|
||||||
*/
|
|
||||||
disableDefaultScope?: boolean;
|
|
||||||
/**
|
|
||||||
* The redirect URL for your application. This is where the provider will
|
|
||||||
* redirect the user after the sign in process. Make sure this URL is
|
|
||||||
* whitelisted in the provider's dashboard.
|
|
||||||
*/
|
|
||||||
redirectURI?: string;
|
|
||||||
/**
|
|
||||||
* The client key of your application
|
|
||||||
* Tiktok Social Provider uses this field instead of clientId
|
|
||||||
*/
|
|
||||||
clientKey?: string;
|
|
||||||
/**
|
|
||||||
* Disable provider from allowing users to sign in
|
|
||||||
* with this provider with an id token sent from the
|
|
||||||
* client.
|
|
||||||
*/
|
|
||||||
disableIdTokenSignIn?: boolean;
|
|
||||||
/**
|
|
||||||
* verifyIdToken function to verify the id token
|
|
||||||
*/
|
|
||||||
verifyIdToken?: (token: string, nonce?: string) => Promise<boolean>;
|
|
||||||
/**
|
|
||||||
* Custom function to get user info from the provider
|
|
||||||
*/
|
|
||||||
getUserInfo?: (token: OAuth2Tokens) => Promise<{
|
|
||||||
user: {
|
|
||||||
id: string;
|
|
||||||
name?: string;
|
|
||||||
email?: string | null;
|
|
||||||
image?: string;
|
|
||||||
emailVerified: boolean;
|
|
||||||
[key: string]: any;
|
|
||||||
};
|
|
||||||
data: any;
|
|
||||||
}>;
|
|
||||||
/**
|
|
||||||
* Custom function to refresh a token
|
|
||||||
*/
|
|
||||||
refreshAccessToken?: (refreshToken: string) => Promise<OAuth2Tokens>;
|
|
||||||
/**
|
|
||||||
* Custom function to map the provider profile to a
|
|
||||||
* user.
|
|
||||||
*/
|
|
||||||
mapProfileToUser?: (profile: Profile) =>
|
|
||||||
| {
|
|
||||||
id?: string;
|
|
||||||
name?: string;
|
|
||||||
email?: string | null;
|
|
||||||
image?: string;
|
|
||||||
emailVerified?: boolean;
|
|
||||||
[key: string]: any;
|
|
||||||
}
|
|
||||||
| Promise<{
|
|
||||||
id?: string;
|
|
||||||
name?: string;
|
|
||||||
email?: string | null;
|
|
||||||
image?: string;
|
|
||||||
emailVerified?: boolean;
|
|
||||||
[key: string]: any;
|
|
||||||
}>;
|
|
||||||
/**
|
|
||||||
* Disable implicit sign up for new users. When set to true for the provider,
|
|
||||||
* sign-in need to be called with with requestSignUp as true to create new users.
|
|
||||||
*/
|
|
||||||
disableImplicitSignUp?: boolean;
|
|
||||||
/**
|
|
||||||
* Disable sign up for new users.
|
|
||||||
*/
|
|
||||||
disableSignUp?: boolean;
|
|
||||||
/**
|
|
||||||
* The prompt to use for the authorization code request
|
|
||||||
*/
|
|
||||||
prompt?:
|
|
||||||
| "select_account"
|
|
||||||
| "consent"
|
|
||||||
| "login"
|
|
||||||
| "none"
|
|
||||||
| "select_account consent";
|
|
||||||
/**
|
|
||||||
* The response mode to use for the authorization code request
|
|
||||||
*/
|
|
||||||
responseMode?: "query" | "form_post";
|
|
||||||
/**
|
|
||||||
* If enabled, the user info will be overridden with the provider user info
|
|
||||||
* This is useful if you want to use the provider user info to update the user info
|
|
||||||
*
|
|
||||||
* @default false
|
|
||||||
*/
|
|
||||||
overrideUserInfoOnSignIn?: boolean;
|
|
||||||
};
|
|
||||||
|
|||||||
194
packages/core/src/oauth2/oauth-provider.ts
Normal file
194
packages/core/src/oauth2/oauth-provider.ts
Normal file
@@ -0,0 +1,194 @@
|
|||||||
|
import type { LiteralString } from "../types";
|
||||||
|
|
||||||
|
export interface OAuth2Tokens {
|
||||||
|
tokenType?: string;
|
||||||
|
accessToken?: string;
|
||||||
|
refreshToken?: string;
|
||||||
|
accessTokenExpiresAt?: Date;
|
||||||
|
refreshTokenExpiresAt?: Date;
|
||||||
|
scopes?: string[];
|
||||||
|
idToken?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type OAuth2UserInfo = {
|
||||||
|
id: string | number;
|
||||||
|
name?: string;
|
||||||
|
email?: string | null;
|
||||||
|
image?: string;
|
||||||
|
emailVerified: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface OAuthProvider<
|
||||||
|
T extends Record<string, any> = Record<string, any>,
|
||||||
|
O extends Record<string, any> = Partial<ProviderOptions>,
|
||||||
|
> {
|
||||||
|
id: LiteralString;
|
||||||
|
createAuthorizationURL: (data: {
|
||||||
|
state: string;
|
||||||
|
codeVerifier: string;
|
||||||
|
scopes?: string[];
|
||||||
|
redirectURI: string;
|
||||||
|
display?: string;
|
||||||
|
loginHint?: string;
|
||||||
|
}) => Promise<URL> | URL;
|
||||||
|
name: string;
|
||||||
|
validateAuthorizationCode: (data: {
|
||||||
|
code: string;
|
||||||
|
redirectURI: string;
|
||||||
|
codeVerifier?: string;
|
||||||
|
deviceId?: string;
|
||||||
|
}) => Promise<OAuth2Tokens>;
|
||||||
|
getUserInfo: (
|
||||||
|
token: OAuth2Tokens & {
|
||||||
|
/**
|
||||||
|
* The user object from the provider
|
||||||
|
* This is only available for some providers like Apple
|
||||||
|
*/
|
||||||
|
user?: {
|
||||||
|
name?: {
|
||||||
|
firstName?: string;
|
||||||
|
lastName?: string;
|
||||||
|
};
|
||||||
|
email?: string;
|
||||||
|
};
|
||||||
|
},
|
||||||
|
) => Promise<{
|
||||||
|
user: OAuth2UserInfo;
|
||||||
|
data: T;
|
||||||
|
} | null>;
|
||||||
|
/**
|
||||||
|
* Custom function to refresh a token
|
||||||
|
*/
|
||||||
|
refreshAccessToken?: (refreshToken: string) => Promise<OAuth2Tokens>;
|
||||||
|
revokeToken?: (token: string) => Promise<void>;
|
||||||
|
/**
|
||||||
|
* Verify the id token
|
||||||
|
* @param token - The id token
|
||||||
|
* @param nonce - The nonce
|
||||||
|
* @returns True if the id token is valid, false otherwise
|
||||||
|
*/
|
||||||
|
verifyIdToken?: (token: string, nonce?: string) => Promise<boolean>;
|
||||||
|
/**
|
||||||
|
* Disable implicit sign up for new users. When set to true for the provider,
|
||||||
|
* sign-in need to be called with with requestSignUp as true to create new users.
|
||||||
|
*/
|
||||||
|
disableImplicitSignUp?: boolean;
|
||||||
|
/**
|
||||||
|
* Disable sign up for new users.
|
||||||
|
*/
|
||||||
|
disableSignUp?: boolean;
|
||||||
|
/**
|
||||||
|
* Options for the provider
|
||||||
|
*/
|
||||||
|
options?: O;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ProviderOptions<Profile extends Record<string, any> = any> = {
|
||||||
|
/**
|
||||||
|
* The client ID of your application.
|
||||||
|
*
|
||||||
|
* This is usually a string but can be any type depending on the provider.
|
||||||
|
*/
|
||||||
|
clientId?: unknown;
|
||||||
|
/**
|
||||||
|
* The client secret of your application
|
||||||
|
*/
|
||||||
|
clientSecret?: string;
|
||||||
|
/**
|
||||||
|
* The scopes you want to request from the provider
|
||||||
|
*/
|
||||||
|
scope?: string[];
|
||||||
|
/**
|
||||||
|
* Remove default scopes of the provider
|
||||||
|
*/
|
||||||
|
disableDefaultScope?: boolean;
|
||||||
|
/**
|
||||||
|
* The redirect URL for your application. This is where the provider will
|
||||||
|
* redirect the user after the sign in process. Make sure this URL is
|
||||||
|
* whitelisted in the provider's dashboard.
|
||||||
|
*/
|
||||||
|
redirectURI?: string;
|
||||||
|
/**
|
||||||
|
* The client key of your application
|
||||||
|
* Tiktok Social Provider uses this field instead of clientId
|
||||||
|
*/
|
||||||
|
clientKey?: string;
|
||||||
|
/**
|
||||||
|
* Disable provider from allowing users to sign in
|
||||||
|
* with this provider with an id token sent from the
|
||||||
|
* client.
|
||||||
|
*/
|
||||||
|
disableIdTokenSignIn?: boolean;
|
||||||
|
/**
|
||||||
|
* verifyIdToken function to verify the id token
|
||||||
|
*/
|
||||||
|
verifyIdToken?: (token: string, nonce?: string) => Promise<boolean>;
|
||||||
|
/**
|
||||||
|
* Custom function to get user info from the provider
|
||||||
|
*/
|
||||||
|
getUserInfo?: (token: OAuth2Tokens) => Promise<{
|
||||||
|
user: {
|
||||||
|
id: string;
|
||||||
|
name?: string;
|
||||||
|
email?: string | null;
|
||||||
|
image?: string;
|
||||||
|
emailVerified: boolean;
|
||||||
|
[key: string]: any;
|
||||||
|
};
|
||||||
|
data: any;
|
||||||
|
}>;
|
||||||
|
/**
|
||||||
|
* Custom function to refresh a token
|
||||||
|
*/
|
||||||
|
refreshAccessToken?: (refreshToken: string) => Promise<OAuth2Tokens>;
|
||||||
|
/**
|
||||||
|
* Custom function to map the provider profile to a
|
||||||
|
* user.
|
||||||
|
*/
|
||||||
|
mapProfileToUser?: (profile: Profile) =>
|
||||||
|
| {
|
||||||
|
id?: string;
|
||||||
|
name?: string;
|
||||||
|
email?: string | null;
|
||||||
|
image?: string;
|
||||||
|
emailVerified?: boolean;
|
||||||
|
[key: string]: any;
|
||||||
|
}
|
||||||
|
| Promise<{
|
||||||
|
id?: string;
|
||||||
|
name?: string;
|
||||||
|
email?: string | null;
|
||||||
|
image?: string;
|
||||||
|
emailVerified?: boolean;
|
||||||
|
[key: string]: any;
|
||||||
|
}>;
|
||||||
|
/**
|
||||||
|
* Disable implicit sign up for new users. When set to true for the provider,
|
||||||
|
* sign-in need to be called with with requestSignUp as true to create new users.
|
||||||
|
*/
|
||||||
|
disableImplicitSignUp?: boolean;
|
||||||
|
/**
|
||||||
|
* Disable sign up for new users.
|
||||||
|
*/
|
||||||
|
disableSignUp?: boolean;
|
||||||
|
/**
|
||||||
|
* The prompt to use for the authorization code request
|
||||||
|
*/
|
||||||
|
prompt?:
|
||||||
|
| "select_account"
|
||||||
|
| "consent"
|
||||||
|
| "login"
|
||||||
|
| "none"
|
||||||
|
| "select_account consent";
|
||||||
|
/**
|
||||||
|
* The response mode to use for the authorization code request
|
||||||
|
*/
|
||||||
|
responseMode?: "query" | "form_post";
|
||||||
|
/**
|
||||||
|
* If enabled, the user info will be overridden with the provider user info
|
||||||
|
* This is useful if you want to use the provider user info to update the user info
|
||||||
|
*
|
||||||
|
* @default false
|
||||||
|
*/
|
||||||
|
overrideUserInfoOnSignIn?: boolean;
|
||||||
|
};
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { betterFetch } from "@better-fetch/fetch";
|
import { betterFetch } from "@better-fetch/fetch";
|
||||||
import type { OAuth2Tokens, ProviderOptions } from "@better-auth/core/oauth2";
|
import type { OAuth2Tokens, ProviderOptions } from "./oauth-provider";
|
||||||
import { base64 } from "@better-auth/utils/base64";
|
import { base64 } from "@better-auth/utils/base64";
|
||||||
|
|
||||||
export function createRefreshAccessTokenRequest({
|
export function createRefreshAccessTokenRequest({
|
||||||
36
packages/core/src/oauth2/utils.ts
Normal file
36
packages/core/src/oauth2/utils.ts
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import { base64Url } from "@better-auth/utils/base64";
|
||||||
|
import type { OAuth2Tokens } from "./oauth-provider";
|
||||||
|
|
||||||
|
export function getOAuth2Tokens(data: Record<string, any>): OAuth2Tokens {
|
||||||
|
const getDate = (seconds: number) => {
|
||||||
|
const now = new Date();
|
||||||
|
return new Date(now.getTime() + seconds * 1000);
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
tokenType: data.token_type,
|
||||||
|
accessToken: data.access_token,
|
||||||
|
refreshToken: data.refresh_token,
|
||||||
|
accessTokenExpiresAt: data.expires_in
|
||||||
|
? getDate(data.expires_in)
|
||||||
|
: undefined,
|
||||||
|
refreshTokenExpiresAt: data.refresh_token_expires_in
|
||||||
|
? getDate(data.refresh_token_expires_in)
|
||||||
|
: undefined,
|
||||||
|
scopes: data?.scope
|
||||||
|
? typeof data.scope === "string"
|
||||||
|
? data.scope.split(" ")
|
||||||
|
: data.scope
|
||||||
|
: [],
|
||||||
|
idToken: data.id_token,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function generateCodeChallenge(codeVerifier: string) {
|
||||||
|
const encoder = new TextEncoder();
|
||||||
|
const data = encoder.encode(codeVerifier);
|
||||||
|
const hash = await crypto.subtle.digest("SHA-256", data);
|
||||||
|
return base64Url.encode(new Uint8Array(hash), {
|
||||||
|
padding: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { betterFetch } from "@better-fetch/fetch";
|
import { betterFetch } from "@better-fetch/fetch";
|
||||||
import { jwtVerify } from "jose";
|
import { jwtVerify } from "jose";
|
||||||
import type { ProviderOptions } from "@better-auth/core/oauth2";
|
import type { ProviderOptions } from "./index";
|
||||||
import { getOAuth2Tokens } from "./utils";
|
import { getOAuth2Tokens } from "./index";
|
||||||
import { base64 } from "@better-auth/utils/base64";
|
import { base64 } from "@better-auth/utils/base64";
|
||||||
|
|
||||||
export function createAuthorizationCodeRequest({
|
export function createAuthorizationCodeRequest({
|
||||||
23
pnpm-lock.yaml
generated
23
pnpm-lock.yaml
generated
@@ -355,7 +355,7 @@ importers:
|
|||||||
version: 12.23.12(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
|
version: 12.23.12(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
|
||||||
geist:
|
geist:
|
||||||
specifier: ^1.4.2
|
specifier: ^1.4.2
|
||||||
version: 1.4.2(next@15.5.2(@babel/core@7.28.4)(@opentelemetry/api@1.9.0)(@playwright/test@1.55.0)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(sass@1.90.0))
|
version: 1.4.2(next@15.5.2(@opentelemetry/api@1.9.0)(@playwright/test@1.55.0)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(sass@1.90.0))
|
||||||
input-otp:
|
input-otp:
|
||||||
specifier: ^1.4.2
|
specifier: ^1.4.2
|
||||||
version: 1.4.2(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
|
version: 1.4.2(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
|
||||||
@@ -599,7 +599,7 @@ importers:
|
|||||||
version: 15.8.3(@oramacloud/client@2.1.4)(@tanstack/react-router@1.131.27(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(algoliasearch@5.36.0)(lucide-react@0.542.0(react@19.1.1))(next@15.5.2(@babel/core@7.28.4)(@opentelemetry/api@1.9.0)(@playwright/test@1.55.0)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(sass@1.90.0))(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(tailwindcss@4.1.13)
|
version: 15.8.3(@oramacloud/client@2.1.4)(@tanstack/react-router@1.131.27(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(algoliasearch@5.36.0)(lucide-react@0.542.0(react@19.1.1))(next@15.5.2(@babel/core@7.28.4)(@opentelemetry/api@1.9.0)(@playwright/test@1.55.0)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(sass@1.90.0))(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(tailwindcss@4.1.13)
|
||||||
geist:
|
geist:
|
||||||
specifier: ^1.4.2
|
specifier: ^1.4.2
|
||||||
version: 1.4.2(next@15.5.2(@babel/core@7.28.4)(@opentelemetry/api@1.9.0)(@playwright/test@1.55.0)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(sass@1.90.0))
|
version: 1.4.2(next@15.5.2(@opentelemetry/api@1.9.0)(@playwright/test@1.55.0)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(sass@1.90.0))
|
||||||
gray-matter:
|
gray-matter:
|
||||||
specifier: ^4.0.3
|
specifier: ^4.0.3
|
||||||
version: 4.0.3
|
version: 4.0.3
|
||||||
@@ -1092,6 +1092,15 @@ importers:
|
|||||||
specifier: ^4.1.5
|
specifier: ^4.1.5
|
||||||
version: 4.1.5
|
version: 4.1.5
|
||||||
devDependencies:
|
devDependencies:
|
||||||
|
'@better-auth/utils':
|
||||||
|
specifier: 0.3.0
|
||||||
|
version: 0.3.0
|
||||||
|
'@better-fetch/fetch':
|
||||||
|
specifier: 'catalog:'
|
||||||
|
version: 1.1.18
|
||||||
|
jose:
|
||||||
|
specifier: ^6.1.0
|
||||||
|
version: 6.1.0
|
||||||
unbuild:
|
unbuild:
|
||||||
specifier: 'catalog:'
|
specifier: 'catalog:'
|
||||||
version: 3.6.1(sass@1.90.0)(typescript@5.9.2)(vue@3.5.19(typescript@5.9.2))
|
version: 3.6.1(sass@1.90.0)(typescript@5.9.2)(vue@3.5.19(typescript@5.9.2))
|
||||||
@@ -15245,7 +15254,7 @@ snapshots:
|
|||||||
postcss: 8.4.49
|
postcss: 8.4.49
|
||||||
resolve-from: 5.0.0
|
resolve-from: 5.0.0
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
expo: 54.0.10(@babel/core@7.28.4)(@expo/metro-runtime@6.1.2)(expo-router@6.0.8)(graphql@16.11.0)(react-native@0.81.4(@babel/core@7.28.4)(@react-native-community/cli@20.0.1(typescript@5.9.2))(@react-native/metro-config@0.81.0(@babel/core@7.28.4))(@types/react@19.1.12)(react@19.1.1))(react@19.1.1)
|
expo: 54.0.10(@babel/core@7.28.4)(@expo/metro-runtime@6.1.2)(expo-router@6.0.8)(graphql@16.11.0)(react-native@0.80.2(@babel/core@7.28.4)(@react-native-community/cli@20.0.1(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.1))(react@19.1.1)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- bufferutil
|
- bufferutil
|
||||||
- supports-color
|
- supports-color
|
||||||
@@ -15330,7 +15339,7 @@ snapshots:
|
|||||||
'@expo/json-file': 10.0.7
|
'@expo/json-file': 10.0.7
|
||||||
'@react-native/normalize-colors': 0.81.4
|
'@react-native/normalize-colors': 0.81.4
|
||||||
debug: 4.4.1
|
debug: 4.4.1
|
||||||
expo: 54.0.10(@babel/core@7.28.4)(@expo/metro-runtime@6.1.2)(expo-router@6.0.8)(graphql@16.11.0)(react-native@0.81.4(@babel/core@7.28.4)(@react-native-community/cli@20.0.1(typescript@5.9.2))(@react-native/metro-config@0.81.0(@babel/core@7.28.4))(@types/react@19.1.12)(react@19.1.1))(react@19.1.1)
|
expo: 54.0.10(@babel/core@7.28.4)(@expo/metro-runtime@6.1.2)(expo-router@6.0.8)(graphql@16.11.0)(react-native@0.80.2(@babel/core@7.28.4)(@react-native-community/cli@20.0.1(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.1))(react@19.1.1)
|
||||||
resolve-from: 5.0.0
|
resolve-from: 5.0.0
|
||||||
semver: 7.7.2
|
semver: 7.7.2
|
||||||
xml2js: 0.6.0
|
xml2js: 0.6.0
|
||||||
@@ -19493,7 +19502,7 @@ snapshots:
|
|||||||
resolve-from: 5.0.0
|
resolve-from: 5.0.0
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@babel/runtime': 7.28.4
|
'@babel/runtime': 7.28.4
|
||||||
expo: 54.0.10(@babel/core@7.28.4)(@expo/metro-runtime@6.1.2)(expo-router@6.0.8)(graphql@16.11.0)(react-native@0.81.4(@babel/core@7.28.4)(@react-native-community/cli@20.0.1(typescript@5.9.2))(@react-native/metro-config@0.81.0(@babel/core@7.28.4))(@types/react@19.1.12)(react@19.1.1))(react@19.1.1)
|
expo: 54.0.10(@babel/core@7.28.4)(@expo/metro-runtime@6.1.2)(expo-router@6.0.8)(graphql@16.11.0)(react-native@0.80.2(@babel/core@7.28.4)(@react-native-community/cli@20.0.1(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.1))(react@19.1.1)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- '@babel/core'
|
- '@babel/core'
|
||||||
- supports-color
|
- supports-color
|
||||||
@@ -21093,7 +21102,7 @@ snapshots:
|
|||||||
|
|
||||||
expo-keep-awake@15.0.7(expo@54.0.10)(react@19.1.1):
|
expo-keep-awake@15.0.7(expo@54.0.10)(react@19.1.1):
|
||||||
dependencies:
|
dependencies:
|
||||||
expo: 54.0.10(@babel/core@7.28.4)(@expo/metro-runtime@6.1.2)(expo-router@6.0.8)(graphql@16.11.0)(react-native@0.81.4(@babel/core@7.28.4)(@react-native-community/cli@20.0.1(typescript@5.9.2))(@react-native/metro-config@0.81.0(@babel/core@7.28.4))(@types/react@19.1.12)(react@19.1.1))(react@19.1.1)
|
expo: 54.0.10(@babel/core@7.28.4)(@expo/metro-runtime@6.1.2)(expo-router@6.0.8)(graphql@16.11.0)(react-native@0.80.2(@babel/core@7.28.4)(@react-native-community/cli@20.0.1(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.1))(react@19.1.1)
|
||||||
react: 19.1.1
|
react: 19.1.1
|
||||||
|
|
||||||
expo-linking@7.1.7(expo@54.0.10)(react-native@0.80.2(@babel/core@7.28.4)(@react-native-community/cli@20.0.1(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.1))(react@19.1.1):
|
expo-linking@7.1.7(expo@54.0.10)(react-native@0.80.2(@babel/core@7.28.4)(@react-native-community/cli@20.0.1(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.1))(react@19.1.1):
|
||||||
@@ -21751,7 +21760,7 @@ snapshots:
|
|||||||
|
|
||||||
function-bind@1.1.2: {}
|
function-bind@1.1.2: {}
|
||||||
|
|
||||||
geist@1.4.2(next@15.5.2(@babel/core@7.28.4)(@opentelemetry/api@1.9.0)(@playwright/test@1.55.0)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(sass@1.90.0)):
|
geist@1.4.2(next@15.5.2(@opentelemetry/api@1.9.0)(@playwright/test@1.55.0)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(sass@1.90.0)):
|
||||||
dependencies:
|
dependencies:
|
||||||
next: 15.5.2(@babel/core@7.28.4)(@opentelemetry/api@1.9.0)(@playwright/test@1.55.0)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(sass@1.90.0)
|
next: 15.5.2(@babel/core@7.28.4)(@opentelemetry/api@1.9.0)(@playwright/test@1.55.0)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(sass@1.90.0)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user