mirror of
https://github.com/LukeHagar/better-auth.git
synced 2025-12-09 12:27:43 +00:00
refactor: append scope and google access type
This commit is contained in:
@@ -10,7 +10,6 @@ import {
|
|||||||
username,
|
username,
|
||||||
} from "better-auth/plugins";
|
} from "better-auth/plugins";
|
||||||
import { reactInvitationEmail } from "./email/invitation";
|
import { reactInvitationEmail } from "./email/invitation";
|
||||||
import { LibsqlDialect } from "@libsql/kysely-libsql";
|
|
||||||
import { reactResetPasswordEmail } from "./email/rest-password";
|
import { reactResetPasswordEmail } from "./email/rest-password";
|
||||||
import { resend } from "./email/resend";
|
import { resend } from "./email/resend";
|
||||||
|
|
||||||
@@ -61,6 +60,7 @@ export const auth = betterAuth({
|
|||||||
google: {
|
google: {
|
||||||
clientId: process.env.GOOGLE_CLIENT_ID || "",
|
clientId: process.env.GOOGLE_CLIENT_ID || "",
|
||||||
clientSecret: process.env.GOOGLE_CLIENT_SECRET || "",
|
clientSecret: process.env.GOOGLE_CLIENT_SECRET || "",
|
||||||
|
accessType: "offline",
|
||||||
},
|
},
|
||||||
discord: {
|
discord: {
|
||||||
clientId: process.env.DISCORD_CLIENT_ID || "",
|
clientId: process.env.DISCORD_CLIENT_ID || "",
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import { generateId } from "../../utils/id";
|
|||||||
import { parseState } from "../../oauth2/state";
|
import { parseState } from "../../oauth2/state";
|
||||||
import { createAuthEndpoint } from "../call";
|
import { createAuthEndpoint } from "../call";
|
||||||
import { HIDE_METADATA } from "../../utils/hide-metadata";
|
import { HIDE_METADATA } from "../../utils/hide-metadata";
|
||||||
import { getAccountTokens } from "../../oauth2/get-account";
|
|
||||||
import { setSessionCookie } from "../../cookies";
|
import { setSessionCookie } from "../../cookies";
|
||||||
import { logger } from "../../utils/logger";
|
import { logger } from "../../utils/logger";
|
||||||
import type { OAuth2Tokens } from "../../oauth2";
|
import type { OAuth2Tokens } from "../../oauth2";
|
||||||
@@ -160,7 +159,10 @@ export const callbackOAuth = createAuthEndpoint(
|
|||||||
accountId: user.id.toString(),
|
accountId: user.id.toString(),
|
||||||
id: `${provider.id}:${user.id}`,
|
id: `${provider.id}:${user.id}`,
|
||||||
userId: dbUser.user.id,
|
userId: dbUser.user.id,
|
||||||
...getAccountTokens(tokens),
|
accessToken: tokens.accessToken,
|
||||||
|
idToken: tokens.idToken,
|
||||||
|
refreshToken: tokens.refreshToken,
|
||||||
|
expiresAt: tokens.accessTokenExpiresAt,
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error("Unable to link account", e);
|
logger.error("Unable to link account", e);
|
||||||
@@ -168,7 +170,10 @@ export const callbackOAuth = createAuthEndpoint(
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
await c.context.internalAdapter.updateAccount(hasBeenLinked.id, {
|
await c.context.internalAdapter.updateAccount(hasBeenLinked.id, {
|
||||||
...getAccountTokens(tokens),
|
accessToken: tokens.accessToken,
|
||||||
|
idToken: tokens.idToken,
|
||||||
|
refreshToken: tokens.refreshToken,
|
||||||
|
expiresAt: tokens.accessTokenExpiresAt,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -180,7 +185,10 @@ export const callbackOAuth = createAuthEndpoint(
|
|||||||
emailVerified,
|
emailVerified,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
...getAccountTokens(tokens),
|
accessToken: tokens.accessToken,
|
||||||
|
idToken: tokens.idToken,
|
||||||
|
refreshToken: tokens.refreshToken,
|
||||||
|
expiresAt: tokens.accessTokenExpiresAt,
|
||||||
providerId: provider.id,
|
providerId: provider.id,
|
||||||
accountId: user.id.toString(),
|
accountId: user.id.toString(),
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,15 +0,0 @@
|
|||||||
import type { OAuth2Tokens } from ".";
|
|
||||||
|
|
||||||
export function getAccountTokens(tokens: OAuth2Tokens) {
|
|
||||||
const accessToken = tokens.accessToken;
|
|
||||||
let refreshToken = tokens.refreshToken;
|
|
||||||
let accessTokenExpiresAt = undefined;
|
|
||||||
try {
|
|
||||||
accessTokenExpiresAt = tokens.accessTokenExpiresAt;
|
|
||||||
} catch {}
|
|
||||||
return {
|
|
||||||
accessToken,
|
|
||||||
refreshToken,
|
|
||||||
expiresAt: accessTokenExpiresAt,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -3,4 +3,3 @@ export * from "./validate-authorization-code";
|
|||||||
export * from "./utils";
|
export * from "./utils";
|
||||||
export * from "./state";
|
export * from "./state";
|
||||||
export * from "./types";
|
export * from "./types";
|
||||||
export * from "./get-account";
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { sha256 } from "oslo/crypto";
|
import { sha256 } from "oslo/crypto";
|
||||||
import { base64url } from "oslo/encoding";
|
import { base64url } from "oslo/encoding";
|
||||||
import type { OAuth2Tokens } from "./types";
|
import type { OAuth2Tokens } from "./types";
|
||||||
|
import { getDate } from "../utils/date";
|
||||||
|
|
||||||
export async function generateCodeChallenge(codeVerifier: string) {
|
export async function generateCodeChallenge(codeVerifier: string) {
|
||||||
const codeChallengeBytes = await sha256(
|
const codeChallengeBytes = await sha256(
|
||||||
@@ -16,8 +17,8 @@ export function getOAuth2Tokens(data: Record<string, any>): OAuth2Tokens {
|
|||||||
tokenType: data.token_type,
|
tokenType: data.token_type,
|
||||||
accessToken: data.access_token,
|
accessToken: data.access_token,
|
||||||
refreshToken: data.refresh_token,
|
refreshToken: data.refresh_token,
|
||||||
accessTokenExpiresAt: data.expires_at
|
accessTokenExpiresAt: data.expires_in
|
||||||
? new Date((Date.now() + data.expires_in) * 1000)
|
? getDate(data.expires_in, "sec")
|
||||||
: undefined,
|
: undefined,
|
||||||
scopes: data?.scope
|
scopes: data?.scope
|
||||||
? typeof data.scope === "string"
|
? typeof data.scope === "string"
|
||||||
|
|||||||
@@ -6,11 +6,9 @@ import { betterFetch } from "@better-fetch/fetch";
|
|||||||
import { generateState, parseState } from "../../oauth2/state";
|
import { generateState, parseState } from "../../oauth2/state";
|
||||||
import { generateCodeVerifier } from "oslo/oauth2";
|
import { generateCodeVerifier } from "oslo/oauth2";
|
||||||
import { logger } from "../../utils/logger";
|
import { logger } from "../../utils/logger";
|
||||||
|
|
||||||
import { parseJWT } from "oslo/jwt";
|
import { parseJWT } from "oslo/jwt";
|
||||||
import { userSchema } from "../../db/schema";
|
import { userSchema } from "../../db/schema";
|
||||||
import { generateId } from "../../utils/id";
|
import { generateId } from "../../utils/id";
|
||||||
import { getAccountTokens } from "../../oauth2/get-account";
|
|
||||||
import { setSessionCookie } from "../../cookies";
|
import { setSessionCookie } from "../../cookies";
|
||||||
import { redirectURLMiddleware } from "../../api/middlewares/redirect";
|
import { redirectURLMiddleware } from "../../api/middlewares/redirect";
|
||||||
import {
|
import {
|
||||||
@@ -421,20 +419,36 @@ export const genericOAuth = (options: GenericOAuthOptions) => {
|
|||||||
accountId: user.data.id,
|
accountId: user.data.id,
|
||||||
id: `${provider.providerId}:${user.data.id}`,
|
id: `${provider.providerId}:${user.data.id}`,
|
||||||
userId: dbUser.user.id,
|
userId: dbUser.user.id,
|
||||||
...getAccountTokens(tokens),
|
accessToken: tokens.accessToken,
|
||||||
|
idToken: tokens.idToken,
|
||||||
|
refreshToken: tokens.refreshToken,
|
||||||
|
expiresAt: tokens.accessTokenExpiresAt,
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(e);
|
console.log(e);
|
||||||
throw ctx.redirect(`${errorURL}?error=failed_linking_account`);
|
throw ctx.redirect(`${errorURL}?error=failed_linking_account`);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
await ctx.context.internalAdapter.updateAccount(
|
||||||
|
hasBeenLinked.id,
|
||||||
|
{
|
||||||
|
accessToken: tokens.accessToken,
|
||||||
|
idToken: tokens.idToken,
|
||||||
|
refreshToken: tokens.refreshToken,
|
||||||
|
expiresAt: tokens.accessTokenExpiresAt,
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
await ctx.context.internalAdapter.createOAuthUser(user.data, {
|
await ctx.context.internalAdapter.createOAuthUser(user.data, {
|
||||||
...getAccountTokens(tokens),
|
|
||||||
id: `${provider.providerId}:${user.data.id}`,
|
id: `${provider.providerId}:${user.data.id}`,
|
||||||
providerId: provider.providerId,
|
providerId: provider.providerId,
|
||||||
accountId: user.data.id,
|
accountId: user.data.id,
|
||||||
|
accessToken: tokens.accessToken,
|
||||||
|
idToken: tokens.idToken,
|
||||||
|
refreshToken: tokens.refreshToken,
|
||||||
|
expiresAt: tokens.accessTokenExpiresAt,
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
const url = new URL(errorURL);
|
const url = new URL(errorURL);
|
||||||
|
|||||||
@@ -52,7 +52,8 @@ export const apple = (options: AppleOptions) => {
|
|||||||
id: "apple",
|
id: "apple",
|
||||||
name: "Apple",
|
name: "Apple",
|
||||||
createAuthorizationURL({ state, scopes, redirectURI }) {
|
createAuthorizationURL({ state, scopes, redirectURI }) {
|
||||||
const _scope = options.scope || scopes || ["email", "name", "openid"];
|
const _scope = scopes || ["email", "name", "openid"];
|
||||||
|
options.scope && _scope.push(...options.scope);
|
||||||
return new URL(
|
return new URL(
|
||||||
`https://appleid.apple.com/auth/authorize?client_id=${
|
`https://appleid.apple.com/auth/authorize?client_id=${
|
||||||
options.clientId
|
options.clientId
|
||||||
|
|||||||
@@ -81,7 +81,8 @@ export const discord = (options: DiscordOptions) => {
|
|||||||
id: "discord",
|
id: "discord",
|
||||||
name: "Discord",
|
name: "Discord",
|
||||||
createAuthorizationURL({ state, scopes, redirectURI }) {
|
createAuthorizationURL({ state, scopes, redirectURI }) {
|
||||||
const _scopes = options.scope || scopes || ["identify", "email"];
|
const _scopes = scopes || ["identify", "email"];
|
||||||
|
options.scope && _scopes.push(...options.scope);
|
||||||
return new URL(
|
return new URL(
|
||||||
`https://discord.com/api/oauth2/authorize?scope=${_scopes.join(
|
`https://discord.com/api/oauth2/authorize?scope=${_scopes.join(
|
||||||
"+",
|
"+",
|
||||||
|
|||||||
@@ -30,7 +30,8 @@ export const dropbox = (options: DropboxOptions) => {
|
|||||||
codeVerifier,
|
codeVerifier,
|
||||||
redirectURI,
|
redirectURI,
|
||||||
}) => {
|
}) => {
|
||||||
const _scopes = options.scope || scopes || ["account_info.read"];
|
const _scopes = scopes || ["account_info.read"];
|
||||||
|
options.scope && _scopes.push(...options.scope);
|
||||||
return await createAuthorizationURL({
|
return await createAuthorizationURL({
|
||||||
id: "dropbox",
|
id: "dropbox",
|
||||||
options,
|
options,
|
||||||
|
|||||||
@@ -22,7 +22,8 @@ export const facebook = (options: FacebookOptions) => {
|
|||||||
id: "facebook",
|
id: "facebook",
|
||||||
name: "Facebook",
|
name: "Facebook",
|
||||||
async createAuthorizationURL({ state, scopes, redirectURI }) {
|
async createAuthorizationURL({ state, scopes, redirectURI }) {
|
||||||
const _scopes = options.scope || scopes || ["email", "public_profile"];
|
const _scopes = scopes || ["email", "public_profile"];
|
||||||
|
options.scope && _scopes.push(...options.scope);
|
||||||
return await createAuthorizationURL({
|
return await createAuthorizationURL({
|
||||||
id: "facebook",
|
id: "facebook",
|
||||||
options,
|
options,
|
||||||
|
|||||||
@@ -58,7 +58,8 @@ export const github = (options: GithubOptions) => {
|
|||||||
id: "github",
|
id: "github",
|
||||||
name: "GitHub",
|
name: "GitHub",
|
||||||
createAuthorizationURL({ state, scopes, codeVerifier, redirectURI }) {
|
createAuthorizationURL({ state, scopes, codeVerifier, redirectURI }) {
|
||||||
const _scopes = options.scope || scopes || ["user:email"];
|
const _scopes = scopes || ["user:email"];
|
||||||
|
options.scope && _scopes.push(...options.scope);
|
||||||
return createAuthorizationURL({
|
return createAuthorizationURL({
|
||||||
id: "github",
|
id: "github",
|
||||||
options,
|
options,
|
||||||
|
|||||||
@@ -31,13 +31,15 @@ export interface GoogleProfile {
|
|||||||
sub: string;
|
sub: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GoogleOptions extends ProviderOptions {}
|
export interface GoogleOptions extends ProviderOptions {
|
||||||
|
accessType?: "offline" | "online";
|
||||||
|
}
|
||||||
|
|
||||||
export const google = (options: GoogleOptions) => {
|
export const google = (options: GoogleOptions) => {
|
||||||
return {
|
return {
|
||||||
id: "google",
|
id: "google",
|
||||||
name: "Google",
|
name: "Google",
|
||||||
createAuthorizationURL({ state, scopes, codeVerifier, redirectURI }) {
|
async createAuthorizationURL({ state, scopes, codeVerifier, redirectURI }) {
|
||||||
if (!options.clientId || !options.clientSecret) {
|
if (!options.clientId || !options.clientSecret) {
|
||||||
logger.error(
|
logger.error(
|
||||||
"Client Id and Client Secret is required for Google. Make sure to provide them in the options.",
|
"Client Id and Client Secret is required for Google. Make sure to provide them in the options.",
|
||||||
@@ -47,9 +49,10 @@ export const google = (options: GoogleOptions) => {
|
|||||||
if (!codeVerifier) {
|
if (!codeVerifier) {
|
||||||
throw new BetterAuthError("codeVerifier is required for Google");
|
throw new BetterAuthError("codeVerifier is required for Google");
|
||||||
}
|
}
|
||||||
const _scopes = options.scope || scopes || ["email", "profile"];
|
const _scopes = scopes || ["email", "profile", "openid"];
|
||||||
|
options.scope && _scopes.push(...options.scope);
|
||||||
|
|
||||||
const url = createAuthorizationURL({
|
const url = await createAuthorizationURL({
|
||||||
id: "google",
|
id: "google",
|
||||||
options,
|
options,
|
||||||
authorizationEndpoint: "https://accounts.google.com/o/oauth2/auth",
|
authorizationEndpoint: "https://accounts.google.com/o/oauth2/auth",
|
||||||
@@ -58,6 +61,8 @@ export const google = (options: GoogleOptions) => {
|
|||||||
codeVerifier,
|
codeVerifier,
|
||||||
redirectURI,
|
redirectURI,
|
||||||
});
|
});
|
||||||
|
options.accessType &&
|
||||||
|
url.searchParams.set("access_type", options.accessType);
|
||||||
return url;
|
return url;
|
||||||
},
|
},
|
||||||
validateAuthorizationCode: async ({ code, codeVerifier, redirectURI }) => {
|
validateAuthorizationCode: async ({ code, codeVerifier, redirectURI }) => {
|
||||||
|
|||||||
@@ -27,7 +27,8 @@ export const linkedin = (options: LinkedInOptions) => {
|
|||||||
id: "linkedin",
|
id: "linkedin",
|
||||||
name: "Linkedin",
|
name: "Linkedin",
|
||||||
createAuthorizationURL: async ({ state, scopes, redirectURI }) => {
|
createAuthorizationURL: async ({ state, scopes, redirectURI }) => {
|
||||||
const _scopes = options.scope || scopes || ["profile", "email", "openid"];
|
const _scopes = scopes || ["profile", "email", "openid"];
|
||||||
|
options.scope && _scopes.push(...options.scope);
|
||||||
return await createAuthorizationURL({
|
return await createAuthorizationURL({
|
||||||
id: "linkedin",
|
id: "linkedin",
|
||||||
options,
|
options,
|
||||||
|
|||||||
@@ -37,8 +37,9 @@ export const microsoft = (options: MicrosoftOptions) => {
|
|||||||
id: "microsoft",
|
id: "microsoft",
|
||||||
name: "Microsoft EntraID",
|
name: "Microsoft EntraID",
|
||||||
createAuthorizationURL(data) {
|
createAuthorizationURL(data) {
|
||||||
const scopes = options.scope ||
|
const scopes = data.scopes || ["openid", "profile", "email", "User.Read"];
|
||||||
data.scopes || ["openid", "profile", "email", "User.Read"];
|
options.scope && scopes.push(...options.scope);
|
||||||
|
|
||||||
return createAuthorizationURL({
|
return createAuthorizationURL({
|
||||||
id: "microsoft",
|
id: "microsoft",
|
||||||
options,
|
options,
|
||||||
|
|||||||
@@ -18,7 +18,8 @@ export const spotify = (options: SpotifyOptions) => {
|
|||||||
id: "spotify",
|
id: "spotify",
|
||||||
name: "Spotify",
|
name: "Spotify",
|
||||||
createAuthorizationURL({ state, scopes, codeVerifier, redirectURI }) {
|
createAuthorizationURL({ state, scopes, codeVerifier, redirectURI }) {
|
||||||
const _scopes = options.scope || scopes || ["user-read-email"];
|
const _scopes = scopes || ["user-read-email"];
|
||||||
|
options.scope && _scopes.push(...options.scope);
|
||||||
return createAuthorizationURL({
|
return createAuthorizationURL({
|
||||||
id: "spotify",
|
id: "spotify",
|
||||||
options,
|
options,
|
||||||
|
|||||||
@@ -31,7 +31,8 @@ export const twitch = (options: TwitchOptions) => {
|
|||||||
id: "twitch",
|
id: "twitch",
|
||||||
name: "Twitch",
|
name: "Twitch",
|
||||||
createAuthorizationURL({ state, scopes, redirectURI }) {
|
createAuthorizationURL({ state, scopes, redirectURI }) {
|
||||||
const _scopes = options.scope || scopes || ["user:read:email", "openid"];
|
const _scopes = scopes || ["user:read:email", "openid"];
|
||||||
|
options.scope && _scopes.push(...options.scope);
|
||||||
return createAuthorizationURL({
|
return createAuthorizationURL({
|
||||||
id: "twitch",
|
id: "twitch",
|
||||||
redirectURI,
|
redirectURI,
|
||||||
|
|||||||
@@ -98,7 +98,8 @@ export const twitter = (options: TwitterOption) => {
|
|||||||
id: "twitter",
|
id: "twitter",
|
||||||
name: "Twitter",
|
name: "Twitter",
|
||||||
createAuthorizationURL(data) {
|
createAuthorizationURL(data) {
|
||||||
const _scopes = options.scope || data.scopes || ["account_info.read"];
|
const _scopes = data.scopes || ["account_info.read"];
|
||||||
|
options.scope && _scopes.push(...options.scope);
|
||||||
return createAuthorizationURL({
|
return createAuthorizationURL({
|
||||||
id: "twitter",
|
id: "twitter",
|
||||||
options,
|
options,
|
||||||
|
|||||||
Reference in New Issue
Block a user