import KeycloakProvider, { type KeycloakProfile } from "next-auth/providers/keycloak"; import { NextAuthOptions } from "next-auth"; import { jwtDecode } from "jwt-decode"; import { type JWT } from "next-auth/jwt"; import { User, Profile } from "next-auth"; import { configs } from "../config/configs"; import { getPatreonMemberships, getKeycloakIdpToken, updateKeycloakUserPatreonEntitlements } from "./patreon"; declare module "next-auth" { interface Session { user: User; token: JWT | undefined; profile: Profile; } interface Profile { realm_access: any; preferred_username: string; username_visibility?: "public" | "private"; } interface Session { roles: any } } declare module "next-auth/jwt" { interface JWT { expires_at: number; access_token: string; refresh_token: string; profile: Profile; } } export const authOptions: NextAuthOptions = { providers: [ KeycloakProvider({ clientId: configs.keycloakClientId, clientSecret: configs.keycloakClientSecret, issuer: configs.keycloakIssuer }) ], callbacks: { async jwt({ token, account, profile }) { // console.log(`jwt() callback. account as follows.`) // console.log(account) // console.log(`jwt() callback. token as follows.`) // console.log(token) console.log(`jwt() callback. profile as follows.`) console.log(profile) if (account) { const decodedAccountToken = jwtDecode(account.access_token as any) as KeycloakProfile console.log(`jwt() callback. decodedAccountToken as follows.`) console.log(decodedAccountToken) token.client_roles = decodedAccountToken.realm_access.roles token.scope = decodedAccountToken.scope token.expires_at = account.expires_at ?? 0; token.access_token = account.access_token!; token.refresh_token = account.refresh_token!; } if (profile) { token.profile = profile } return token; }, async session({ session, token, trigger }) { // console.log(`Token interceptor to add token info to the session to use on the pages. trigger=${trigger}`) // console.log(JSON.stringify(token, null, 2)) // console.log('session as follows') // console.log(JSON.stringify(session, null, 2)) const userId = token?.sub if (!userId) throw new Error('failed to get userId from token.sub'); // side effect which gets user's patreon tiers and adds them to the appropriate keycloak group await updateKeycloakUserPatreonEntitlements(token) // session.account = token.account session.profile = token.profile session.roles = token.client_roles session.token = token return session } } } // async jwt({ token, account, profile }) { // try { // if (account) { // const decodedToken = jwtDecode(account.access_token as any) // if (token == null){ // throw new Error("Unable to decode token") // } // console.log('decodedToken as follows') // console.log(JSON.stringify(decodedToken, null, 2)) // // Do something here to add more info, maybe just overwrite profile (thats the one that should have this info) // profile = decodedToken // token.account = account // } // if (profile) { // console.log('profile as follows') // console.log(profile) // token.profile = profile // // Then do here the assignation of roles elements to token so session has access // // This can be modified so uses by client, realm or account BE AWARE OF THAT! // // Modify the "resource_access['next-auth-AFB']" value to the one your resource/realm/accout // // json scope roles you need // // While the info is already on profile, we could make a new key on the json response of session // const clientRoles = profile.realm_access.roles // token.client_roles = clientRoles // } // } catch (error) { // console.log(error) // } // return token // },