refactor: append scope and google access type

This commit is contained in:
Bereket Engida
2024-10-24 11:31:24 +03:00
parent 36b4c729af
commit 3f09cb072f
17 changed files with 64 additions and 42 deletions

View File

@@ -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 || "",

View File

@@ -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(),
},

View File

@@ -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,
};
}

View File

@@ -3,4 +3,3 @@ export * from "./validate-authorization-code";
export * from "./utils";
export * from "./state";
export * from "./types";
export * from "./get-account";

View File

@@ -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"

View File

@@ -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);

View File

@@ -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

View File

@@ -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(
"+",

View File

@@ -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,

View File

@@ -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,

View File

@@ -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,

View File

@@ -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 }) => {

View File

@@ -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,

View File

@@ -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,

View File

@@ -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,

View File

@@ -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,

View File

@@ -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,