Files
web/auth.ts
Linus Flood 814b010569 Merged in feat/curity-changes (pull request #1190)
Feat/curity changes

* Changed curity stuff

* Use env.var

* Merge branch 'master' into feat/curity-changes

* Merged master into feat/curity-changes
2025-01-20 11:44:58 +00:00

232 lines
6.5 KiB
TypeScript

import NextAuth, { type NextAuthConfig, type User } from "next-auth"
import { PRE_REFRESH_TIME_IN_SECONDS } from "@/constants/auth"
import { env } from "@/env/server"
import { LoginTypeEnum } from "./types/components/tracking"
import type { JWT } from "next-auth/jwt"
import type { OIDCConfig } from "next-auth/providers"
function getLoginType(user: User) {
if (user?.login_with.includes("@")) {
return LoginTypeEnum.email
} else if (user?.login_with.toLowerCase() == LoginTypeEnum["email link"]) {
return LoginTypeEnum["email link"]
} else {
return LoginTypeEnum["membership number"]
}
}
async function refreshTokens(token: JWT) {
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,
})
const expiresAt = new_tokens.expires_in
? Date.now() + new_tokens.expires_in * 1000
: undefined
return {
...token,
access_token: new_tokens.access_token,
expires_at: expiresAt,
refresh_token: new_tokens.refresh_token,
}
} catch (error) {
console.log("token-debug Error thrown when trying to refresh", {
error,
sub: token.sub,
})
return {
...token,
error: "RefreshAccessTokenError" as const,
}
}
}
const curityProvider = {
id: "curity",
name: "Curity",
type: "oidc",
clientId: env.CURITY_CLIENT_ID_USER,
clientSecret: env.CURITY_CLIENT_SECRET_USER,
issuer: env.CURITY_ISSUER_SERVICE,
authorization: {
url: `${env.CURITY_ISSUER_USER}/oauth/v2/authorize`,
},
token: {
url: `${env.CURITY_ISSUER_USER}/oauth/v2/token`,
},
userinfo: {
url: `${env.CURITY_ISSUER_USER}/oauth/v2/userinfo`,
},
profile(profile: User) {
return {
id: profile.id,
sub: profile.sub,
given_name: profile.given_name,
login_with: profile.login_with,
}
},
} satisfies OIDCConfig<User>
export const config = {
basePath: "/api/web/auth",
debug: env.NEXTAUTH_DEBUG,
providers: [curityProvider],
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 }) {
console.log(`[auth] deciding redirect URL`, { baseUrl, url })
if (url.startsWith("/")) {
console.log(`[auth] relative URL accepted, returning: ${baseUrl}${url}`)
// 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)
) {
console.log(`[auth] subdomain URL accepted, returning: ${url}`)
// Allows any subdomains on all top level domains above
return url
} else if (parsedUrl.origin === baseUrl) {
// Allows callback URLs on the same origin
console.log(`[auth] origin URL accepted, returning: ${url}`)
return url
}
} catch (e) {
console.error(`[auth] error parsing incoming URL for redirection`, e)
}
}
console.log(`[auth] URL denied, returning base URL: ${baseUrl}`)
return baseUrl
},
async authorized({ auth, request }) {
return true
},
async jwt({ account, session, token, trigger, user, profile }) {
const loginType = getLoginType(user)
if (trigger === "signIn" && account) {
const mfa_scope = profile?.amr == "urn:com:scandichotels:scandic-otp"
const tokenExpiry = account.expires_at
? account.expires_at * 1000
: undefined
const mfa_expires_at = mfa_scope && tokenExpiry ? tokenExpiry : 0
return {
access_token: account.access_token,
expires_at: tokenExpiry,
refresh_token: account.refresh_token,
loginType,
mfa_scope: mfa_scope,
mfa_expires_at: mfa_expires_at,
}
} else if (
token.expires_at &&
Date.now() > token.expires_at - PRE_REFRESH_TIME_IN_SECONDS * 1000 &&
session?.doRefresh
) {
return refreshTokens(token)
}
return token
},
},
// 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)