|
1 | 1 | import jwt from "jsonwebtoken";
|
2 |
| -import type { NextAuthConfig, Profile } from "next-auth"; |
| 2 | +import type { NextAuthConfig } from "next-auth"; |
| 3 | +// eslint-disable-next-line @typescript-eslint/no-unused-vars |
3 | 4 | import type { JWT } from "next-auth/jwt";
|
4 | 5 | import github from "next-auth/providers/github";
|
5 | 6 | import keycloak from "next-auth/providers/keycloak";
|
6 | 7 | import nodemailer from "next-auth/providers/nodemailer";
|
7 |
| - |
8 |
| -declare module "next-auth/jwt" { |
9 |
| - interface JWT extends Pick<Profile, "roles"> { |
10 |
| - id?: string; |
11 |
| - } |
12 |
| -} |
| 8 | +import type { User as PayloadUser } from "payload/generated-types"; |
13 | 9 |
|
14 | 10 | declare module "next-auth" {
|
15 |
| - interface Profile { |
16 |
| - roles?: string[]; |
17 |
| - } |
18 | 11 | // eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
19 |
| - interface User extends Pick<JWT, "id" | "roles"> {} |
| 12 | + interface User |
| 13 | + extends Partial<Omit<PayloadUser, "accounts" | "sessions" | "verificationTokens">> {} |
| 14 | +} |
| 15 | +declare module "next-auth/jwt" { |
| 16 | + // eslint-disable-next-line @typescript-eslint/no-empty-object-type |
| 17 | + interface JWT |
| 18 | + extends Partial< |
| 19 | + Pick< |
| 20 | + PayloadUser, |
| 21 | + "id" | "additionalUserDatabaseField" | "additionalUserVirtualField" | "roles" |
| 22 | + > |
| 23 | + > {} |
20 | 24 | }
|
21 | 25 |
|
22 | 26 | export const authConfig: NextAuthConfig = {
|
23 | 27 | theme: { logo: "https://authjs.dev/img/logo-sm.png" },
|
24 | 28 | providers: [
|
25 | 29 | github({
|
26 | 30 | allowDangerousEmailAccountLinking: true,
|
| 31 | + /** |
| 32 | + * Add additional fields to the user on first sign in |
| 33 | + */ |
27 | 34 | profile(profile) {
|
28 |
| - profile.roles = ["user"]; // Extend the profile |
29 | 35 | return {
|
| 36 | + // Default fields (@see https://github.com/nextauthjs/next-auth/blob/main/packages/core/src/providers/github.ts#L176) |
30 | 37 | id: profile.id.toString(),
|
31 | 38 | name: profile.name ?? profile.login,
|
32 |
| - email: profile.email, |
| 39 | + email: profile.email!, |
33 | 40 | image: profile.avatar_url,
|
34 |
| - roles: ["user"], // Extend the user |
| 41 | + // Custom fields |
| 42 | + additionalUserDatabaseField: `Create by github provider profile callback at ${new Date().toISOString()}`, |
| 43 | + }; |
| 44 | + }, |
| 45 | + account(tokens) { |
| 46 | + return { |
| 47 | + ...tokens, |
| 48 | + additionalAccountDatabaseField: `Create by github provider profile callback at ${new Date().toISOString()}`, |
35 | 49 | };
|
36 | 50 | },
|
37 | 51 | }),
|
38 | 52 | keycloak({
|
39 | 53 | allowDangerousEmailAccountLinking: true,
|
40 |
| - profile(profile, tokens) { |
41 |
| - // Add roles to the profile |
42 |
| - if (tokens.access_token) { |
43 |
| - const decodedToken = jwt.decode(tokens.access_token); |
44 |
| - if (decodedToken && typeof decodedToken !== "string") { |
45 |
| - profile.roles = decodedToken.resource_access?.[process.env.AUTH_KEYCLOAK_ID!]?.roles; // Extend the profile |
46 |
| - } |
47 |
| - } |
| 54 | + /** |
| 55 | + * Add additional fields to the user on first sign in |
| 56 | + */ |
| 57 | + profile(profile) { |
48 | 58 | return {
|
| 59 | + // Default fields |
49 | 60 | id: profile.sub,
|
50 | 61 | name: profile.name,
|
51 | 62 | email: profile.email,
|
52 | 63 | image: profile.picture,
|
53 |
| - roles: profile.roles ?? [], // Extend the user |
| 64 | + // Custom fields |
| 65 | + locale: profile.locale, |
| 66 | + additionalUserDatabaseField: `Create by keycloak provider profile callback at ${new Date().toISOString()}`, |
| 67 | + }; |
| 68 | + }, |
| 69 | + account(tokens) { |
| 70 | + return { |
| 71 | + ...tokens, |
| 72 | + additionalAccountDatabaseField: `Create by keycloak provider profile callback at ${new Date().toISOString()}`, |
54 | 73 | };
|
55 | 74 | },
|
56 | 75 | }),
|
57 | 76 | nodemailer({
|
58 | 77 | server: process.env.EMAIL_SERVER,
|
59 | 78 | from: process.env.EMAIL_FROM,
|
| 79 | + /* sendVerificationRequest: ({ url }) => { |
| 80 | + console.log("nodemailer:", url); |
| 81 | + }, */ |
60 | 82 | }),
|
61 | 83 | ],
|
62 |
| - /* session: { |
| 84 | + session: { |
63 | 85 | strategy: "jwt",
|
64 |
| - }, */ |
| 86 | + }, |
65 | 87 | callbacks: {
|
66 |
| - jwt: ({ token, user, profile }) => { |
67 |
| - // Include user id in the JWT token |
| 88 | + jwt: ({ token, user, account, trigger }) => { |
| 89 | + //console.log("callbacks.jwt", token, user, account); |
| 90 | + |
| 91 | + /** |
| 92 | + * For jwt session strategy, we need to forward additional fields to the token |
| 93 | + */ |
68 | 94 | if (user) {
|
69 |
| - token.id = user.id; |
| 95 | + if (user.id) { |
| 96 | + token.id = user.id; |
| 97 | + } |
| 98 | + token.additionalUserDatabaseField = user.additionalUserDatabaseField; |
70 | 99 | }
|
71 |
| - // Include roles in the JWT token |
72 |
| - if (profile) { |
73 |
| - token.roles = profile.roles; |
| 100 | + |
| 101 | + // Add virtual field to the token |
| 102 | + token.additionalUserVirtualField = `Create by jwt callback at ${new Date().toISOString()}`; |
| 103 | + |
| 104 | + /** |
| 105 | + * Add roles to the token |
| 106 | + * - Extract roles from the token for keycloak provider |
| 107 | + * - otherwise use default roles ["user"] |
| 108 | + */ |
| 109 | + if (trigger === "signIn" || trigger === "signUp") { |
| 110 | + const roles: string[] = ["user"]; |
| 111 | + if (account?.provider === "keycloak" && account.access_token) { |
| 112 | + const decodedToken = jwt.decode(account.access_token); |
| 113 | + if (decodedToken && typeof decodedToken !== "string") { |
| 114 | + roles.push( |
| 115 | + ...(decodedToken.resource_access?.[process.env.AUTH_KEYCLOAK_ID!]?.roles ?? []), |
| 116 | + ); |
| 117 | + } |
| 118 | + } |
| 119 | + token.roles = [...new Set(roles)]; |
74 | 120 | }
|
| 121 | + |
75 | 122 | return token;
|
76 | 123 | },
|
77 |
| - session: ({ session, user, token }) => { |
78 |
| - // session strategy: "jwt" |
| 124 | + session: ({ session, token }) => { |
| 125 | + //console.log("callbacks.session", session, user, token); |
| 126 | + |
| 127 | + /** |
| 128 | + * For jwt session strategy, we need to forward additional fields to the session |
| 129 | + */ |
79 | 130 | if (token) {
|
80 | 131 | if (token.id) {
|
81 | 132 | session.user.id = token.id;
|
82 | 133 | }
|
| 134 | + |
| 135 | + session.user.additionalUserDatabaseField = token.additionalUserDatabaseField; |
| 136 | + session.user.additionalUserVirtualField = token.additionalUserVirtualField; |
| 137 | + |
83 | 138 | session.user.roles = token.roles;
|
84 | 139 | }
|
85 |
| - // session strategy: "database" |
86 |
| - if (user) { |
87 |
| - session.user.id = user.id; |
88 |
| - } |
| 140 | + |
89 | 141 | return session;
|
90 | 142 | },
|
91 |
| - /* signIn: async () => { |
92 |
| - console.log("signIn auth.ts"); |
93 |
| - return true; |
94 |
| - }, */ |
95 | 143 | authorized: ({ auth }) => {
|
96 | 144 | // Logged in users are authenticated, otherwise redirect to login page
|
97 | 145 | return !!auth;
|
98 | 146 | },
|
99 | 147 | },
|
| 148 | + /* events: { |
| 149 | + signIn: () => { |
| 150 | + console.log("original events.signIn"); |
| 151 | + }, |
| 152 | + }, */ |
100 | 153 | };
|
0 commit comments