feat(SW-3541): Do social login after login to SAS * feat(auth): wip social login via curity * Setup social login auth flow * Merge branch 'master' of bitbucket.org:scandic-swap/web into feature/curity-social-login * Added support for getting scandic tokens and refresh them * feat: Enhance social login and session management with auto-refresh and improved error handling * Merge branch 'master' of bitbucket.org:scandic-swap/web into feature/curity-social-login * wrap layout in suspense * revert app/layout.tsx * fix import * cleanup * merge * merge * dont pass client_secret in the url to curity * add state validation when doing social login through /authorize * remove debug logging Approved-by: Anton Gunnarsson
145 lines
3.9 KiB
TypeScript
145 lines
3.9 KiB
TypeScript
import NextAuth, { type NextAuthConfig } from "next-auth"
|
|
import Auth0Provider from "next-auth/providers/auth0"
|
|
|
|
import { createLogger } from "@scandic-hotels/common/logger/createLogger"
|
|
import { safeTry } from "@scandic-hotels/common/utils/safeTry"
|
|
import { getEuroBonusProfileData } from "@scandic-hotels/trpc/routers/partners/sas/getEuroBonusProfile"
|
|
|
|
import { env } from "@/env/server"
|
|
|
|
const authLogger = createLogger("auth")
|
|
/*
|
|
TODO: Get info for SAS token timeout and accordingly adjust pre-refresh time move to common/contants
|
|
Do we need to handle refresh tokens at all, isn't that handled by the Auth0 provider?
|
|
Needs to be verified
|
|
*/
|
|
|
|
const sasProvider = Auth0Provider({
|
|
id: "sas",
|
|
name: "SAS",
|
|
clientId: env.SAS_AUTH_CLIENTID,
|
|
issuer: env.SAS_AUTH_ENDPOINT,
|
|
checks: ["state"],
|
|
authorization: {
|
|
params: {
|
|
audience: "eb-partner-api",
|
|
},
|
|
},
|
|
client: {
|
|
token_endpoint_auth_method: "none",
|
|
},
|
|
})
|
|
|
|
const config: NextAuthConfig = {
|
|
basePath: "/api/web/auth",
|
|
debug: env.NEXTAUTH_DEBUG,
|
|
trustHost: true,
|
|
providers: [sasProvider],
|
|
session: {
|
|
strategy: "jwt",
|
|
},
|
|
cookies: {
|
|
sessionToken: {
|
|
name: "sas-session",
|
|
},
|
|
},
|
|
callbacks: {
|
|
async signIn() {
|
|
return true
|
|
},
|
|
async jwt(params) {
|
|
if (params.trigger === "signIn") {
|
|
const accessToken = params.account?.access_token
|
|
// expires_at is in seconds for SAS, we need milliseconds
|
|
const expiresAt = params.account?.expires_at
|
|
? params.account.expires_at * 1000
|
|
: null
|
|
|
|
if (!accessToken) {
|
|
throw new Error("AuthError: Missing access token")
|
|
}
|
|
|
|
if (!expiresAt) {
|
|
throw new Error("AuthError: Missing expiry time")
|
|
}
|
|
const [eurobonusProfile, error] = await safeTry(
|
|
getEuroBonusProfileData({ accessToken, loginType: "sas" })
|
|
)
|
|
|
|
if (error) {
|
|
authLogger.error("Failed to fetch EuroBonus profile", error)
|
|
}
|
|
|
|
return {
|
|
...params.token,
|
|
isLinked: eurobonusProfile?.linkStatus === "LINKED",
|
|
loginType: "sas",
|
|
access_token: accessToken,
|
|
expires_at: expiresAt,
|
|
}
|
|
}
|
|
|
|
return params.token
|
|
},
|
|
async session({ session, token }) {
|
|
return {
|
|
...session,
|
|
error: token.error,
|
|
user: session.user
|
|
? {
|
|
...session.user,
|
|
id: token.sub,
|
|
isLinked: token.isLinked,
|
|
}
|
|
: undefined,
|
|
token: {
|
|
loginType: "sas",
|
|
access_token: token.access_token,
|
|
expires_at: token.expires_at,
|
|
error: token.error,
|
|
},
|
|
}
|
|
},
|
|
async redirect({ baseUrl, url }) {
|
|
authLogger.debug(`[auth] deciding redirect URL`, { baseUrl, url })
|
|
if (url.startsWith("/")) {
|
|
authLogger.debug(
|
|
`[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\.com$/.test(parsedUrl.hostname)) {
|
|
authLogger.debug(`[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
|
|
authLogger.debug(`[auth] origin URL accepted, returning: ${url}`)
|
|
return url
|
|
}
|
|
} catch (e) {
|
|
authLogger.error(
|
|
`[auth] error parsing incoming URL for redirection`,
|
|
e
|
|
)
|
|
}
|
|
}
|
|
authLogger.debug(`[auth] URL denied, returning base URL: ${baseUrl}`)
|
|
|
|
return baseUrl
|
|
},
|
|
},
|
|
}
|
|
|
|
export const {
|
|
handlers: { GET, POST },
|
|
signIn,
|
|
signOut,
|
|
} = NextAuth(config)
|
|
|
|
export const { auth } = NextAuth(config)
|