learnhouse/apps/web/app/auth/options.ts
WhiteX d32389a8ef Add LearnHouse Deployment Isolation Toolkit and debugging tools
- Introduced comprehensive documentation for diagnosing and fixing deployment isolation issues between DEV and LIVE instances.
- Implemented enhanced debug API endpoints for deployment verification, URL hardcoding detection, cookie isolation testing, and session configuration checks.
- Created scripts for visual demonstration of cookie isolation, enhanced debugging deployment, and verification of NextAuth cookie isolation.
- Developed a master isolation verification script to run all isolation checks in sequence and summarize results.
- Added detailed README and environment variable guidelines for proper deployment isolation.
2025-10-15 08:01:08 -04:00

146 lines
4.8 KiB
TypeScript

import {
getNewAccessTokenUsingRefreshTokenServer,
getUserSession,
loginAndGetToken,
loginWithOAuthToken,
} from '@services/auth/auth'
import { LEARNHOUSE_TOP_DOMAIN, getUriWithOrg } from '@services/config/config'
import { getResponseMetadata } from '@services/utils/ts/requests'
import CredentialsProvider from 'next-auth/providers/credentials'
import GoogleProvider from 'next-auth/providers/google'
// Add type declarations at the top of the file
declare global {
var sessionCache: {
[key: string]: {
data: any;
timestamp: number;
};
};
}
export const isDevEnv = LEARNHOUSE_TOP_DOMAIN == 'localhost' ? true : false
export const nextAuthOptions = {
debug: true,
providers: [
CredentialsProvider({
// The name to display on the sign in form (e.g. 'Sign in with...')
name: 'Credentials',
// The credentials is used to generate a suitable form on the sign in page.
// You can specify whatever fields you are expecting to be submitted.
// e.g. domain, username, password, 2FA token, etc.
// You can pass any HTML attribute to the <input> tag through the object.
credentials: {
email: { label: 'Email', type: 'text', placeholder: 'jsmith' },
password: { label: 'Password', type: 'password' },
},
async authorize(credentials, req) {
// logic to verify if user exists
let unsanitized_req = await loginAndGetToken(
credentials?.email,
credentials?.password
)
let res = await getResponseMetadata(unsanitized_req)
if (res.success) {
// If login failed, then this is the place you could do a registration
return res.data
} else {
return null
}
},
}),
GoogleProvider({
clientId: process.env.LEARNHOUSE_GOOGLE_CLIENT_ID || '',
clientSecret: process.env.LEARNHOUSE_GOOGLE_CLIENT_SECRET || '',
}),
],
pages: {
signIn: getUriWithOrg('auth', '/'),
verifyRequest: getUriWithOrg('auth', '/'),
error: getUriWithOrg('auth', '/'), // Error code passed in query string as ?error=
},
cookies: {
sessionToken: {
name: `${!isDevEnv ? '__Secure-' : ''}next-auth.session-token`,
options: {
httpOnly: true,
sameSite: 'lax',
path: '/',
// When working on localhost or with different domains, use the current domain instead of a shared top domain
domain: process.env.LEARNHOUSE_COOKIE_DOMAIN || (LEARNHOUSE_TOP_DOMAIN === 'localhost' ? undefined : `.${LEARNHOUSE_TOP_DOMAIN}`),
secure: !isDevEnv,
},
},
},
callbacks: {
async jwt({ token, user, account }: any) {
// First sign in with Credentials provider
if (account?.provider == 'credentials' && user) {
token.user = user;
}
// Sign up with Google
if (account?.provider == 'google' && user) {
let unsanitized_req = await loginWithOAuthToken(
user.email,
'google',
account.access_token
);
let userFromOAuth = await getResponseMetadata(unsanitized_req);
token.user = userFromOAuth.data;
}
// Refresh token only if it's close to expiring (1 minute before expiry)
if (token?.user?.tokens) {
const tokenExpiry = token.user.tokens.expiry || 0;
const oneMinute = 1 * 60 * 1000;
if (Date.now() + oneMinute >= tokenExpiry) {
const RefreshedToken = await getNewAccessTokenUsingRefreshTokenServer(
token?.user?.tokens?.refresh_token
);
token = {
...token,
user: {
...token.user,
tokens: {
...token.user.tokens,
access_token: RefreshedToken.access_token,
expiry: Date.now() + (60 * 60 * 1000), // 1 hour from now
},
},
};
}
}
return token;
},
async session({ session, token }: any) {
// Include user information in the session
if (token.user) {
// Cache the session for 1 minute to refresh every minute
const cacheKey = `user_session_${token.user.tokens.access_token}`;
let cachedSession = global.sessionCache?.[cacheKey];
if (cachedSession && Date.now() - cachedSession.timestamp < 1 * 60 * 1000) {
return cachedSession.data;
}
let api_SESSION = await getUserSession(token.user.tokens.access_token);
session.user = api_SESSION.user;
session.roles = api_SESSION.roles;
session.tokens = token.user.tokens;
// Cache the session
if (!global.sessionCache) {
global.sessionCache = {};
}
global.sessionCache[cacheKey] = {
data: session,
timestamp: Date.now()
};
}
return session;
},
},
}