mirror of
https://github.com/rzmk/learnhouse.git
synced 2025-12-18 11:59:26 +00:00
- 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.
146 lines
4.8 KiB
TypeScript
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;
|
|
},
|
|
},
|
|
}
|