import NextAuth from "next-auth" import { env } from "@/env/server" import { LoginTypeEnum } from "./types/components/tracking" import type { NextAuthConfig, User } from "next-auth" import type { OIDCConfig } from "next-auth/providers" function getLoginType(user: User) { // TODO: handle magic link, should be enough to just check for Nonce. // if (user?.nonce) { // return LoginTypeEnum.MagicLink // } if (user?.login_with.includes("@")) { return LoginTypeEnum.email } else { return LoginTypeEnum["membership number"] } } const customProvider = { clientId: env.CURITY_CLIENT_ID_USER, clientSecret: env.CURITY_CLIENT_SECRET_USER, id: "curity", name: "Curity", type: "oidc", // FIXME: This is incorrect. We should not hard code this. // It should be ${env.CURITY_ISSUER_USER}. // This change requires sync between Curity deploy and CurrentWeb and NewWeb. issuer: "https://scandichotels.com", authorization: { url: `${env.CURITY_ISSUER_USER}/oauth/v2/authorize`, params: { scope: ["openid", "profile"].join(" "), /** * The `acr_values` param is used to make Curity display the proper login * page for Scandic. Without the parameter Curity presents some choices * to the user which we do not want. */ acr_values: "acr", }, }, token: { url: `${env.CURITY_ISSUER_USER}/oauth/v2/token`, }, userinfo: { url: `${env.CURITY_ISSUER_USER}/oauth/v2/userinfo`, }, profile(profile) { return { id: profile.id, sub: profile.sub, given_name: profile.given_name, login_with: profile.login_with, } }, } satisfies OIDCConfig export const config = { debug: env.NEXTAUTH_DEBUG, providers: [customProvider], redirectProxyUrl: env.NEXTAUTH_REDIRECT_PROXY_URL, trustHost: true, session: { strategy: "jwt", }, callbacks: { async signIn() { return true }, async session({ session, token }) { session.error = token.error if (session.user) { return { ...session, token, user: { ...session.user, id: token.sub, }, } } return session }, async redirect({ baseUrl, url }) { if (url.startsWith("/")) { // Allows relative callback URLs return `${baseUrl}${url}` } else { // Assume absolute URL try { const parsedUrl = new URL(url) if ( /\.scandichotels\.(dk|de|com|fi|no|se)$/.test(parsedUrl.hostname) ) { // Allows any subdomains on all top level domains above return url } else if (parsedUrl.origin === baseUrl) { // Allows callback URLs on the same origin return url } } catch (e) { console.error("Error in auth redirect callback") console.error(e) } } return baseUrl }, async authorized({ auth, request }) { return true }, async jwt({ account, session, token, trigger, user }) { const loginType = getLoginType(user) if (account) { return { access_token: account.access_token, expires_at: account.expires_at ? account.expires_at * 1000 : undefined, refresh_token: account.refresh_token, loginType, } } else if (Date.now() < token.expires_at) { return token } else { try { console.log( "token-debug Access token expired, trying to refresh it.", { expires_at: token.expires_at, sub: token.sub, token: token.access_token, } ) const response = await fetch( `${env.CURITY_ISSUER_USER}/oauth/v2/token`, { body: new URLSearchParams({ client_id: env.CURITY_CLIENT_ID_USER, client_secret: env.CURITY_CLIENT_SECRET_USER, grant_type: "refresh_token", refresh_token: token.refresh_token, }), headers: { "Content-Type": "application/x-www-form-urlencoded", }, method: "POST", } ) const new_tokens = await response.json() if (!response.ok) { console.log("token-debug Token response was not ok", { status: response.status, statusText: response.statusText, sub: token.sub, }) throw new_tokens } console.log("token-debug Successfully got new token(s)", { expires_at: new_tokens.expires_at, got_new_refresh_token: new_tokens.refresh_token !== token.refresh_token, got_new_access_token: new_tokens.access_token !== token.access_token, sub: token.sub, }) return { ...token, access_token: new_tokens.access_token, expires_at: new_tokens.expires_at, refresh_token: new_tokens.refresh_token ?? token.refresh_token, } } catch (error) { console.log("token-debug Error thrown when trying to refresh", { error, sub: token.sub, }) return { ...token, error: "RefreshAccessTokenError" as const, } } } }, }, // events: { // async signIn() { // console.log("#### SIGNIN EVENT ARGS ######") // console.log(arguments) // console.log("#### END - SIGNIN EVENT ARGS ######") // }, // async signOut() { // console.log("#### SIGNOUT EVENT ARGS ######") // console.log(arguments) // console.log("#### END - SIGNOUT EVENT ARGS ######") // }, // async session() { // console.log("#### SESSION EVENT ARGS ######") // console.log(arguments) // console.log("#### END - SESSION EVENT ARGS ######") // }, // }, // logger: { // error(code, ...message) { // console.info("ERROR LOGGER") // console.error(code, message) // }, // warn(code, ...message) { // console.info("WARN LOGGER") // console.warn(code, message) // }, // debug(code, ...message) { // console.info("DEBUG LOGGER") // console.debug(code, message) // }, // }, } satisfies NextAuthConfig export const { handlers: { GET, POST }, auth, signIn, signOut, } = NextAuth(config)