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