payload-zitadel-plugin 0.3.9 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/README.md +57 -25
  2. package/dist/components/server/LoginButton/index.d.ts +1 -1
  3. package/dist/components/server/LoginButton/index.d.ts.map +1 -1
  4. package/dist/components/server/LoginButton/index.js +4 -2
  5. package/dist/components/server/LoginButton/index.js.map +1 -1
  6. package/dist/constants.d.ts +34 -10
  7. package/dist/constants.d.ts.map +1 -1
  8. package/dist/constants.js +31 -10
  9. package/dist/constants.js.map +1 -1
  10. package/dist/handlers/authorize.d.ts +2 -2
  11. package/dist/handlers/authorize.d.ts.map +1 -1
  12. package/dist/handlers/authorize.js +18 -26
  13. package/dist/handlers/authorize.js.map +1 -1
  14. package/dist/handlers/callback.d.ts +2 -3
  15. package/dist/handlers/callback.d.ts.map +1 -1
  16. package/dist/handlers/callback.js +128 -39
  17. package/dist/handlers/callback.js.map +1 -1
  18. package/dist/index.d.ts +2 -2
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +36 -51
  21. package/dist/index.js.map +1 -1
  22. package/dist/strategy.d.ts +2 -2
  23. package/dist/strategy.d.ts.map +1 -1
  24. package/dist/strategy.js +20 -42
  25. package/dist/strategy.js.map +1 -1
  26. package/dist/types.d.ts +62 -48
  27. package/dist/types.d.ts.map +1 -1
  28. package/dist/types.js.map +1 -1
  29. package/dist/utils/index.d.ts +4 -0
  30. package/dist/utils/index.d.ts.map +1 -0
  31. package/dist/utils/index.js +5 -0
  32. package/dist/utils/index.js.map +1 -0
  33. package/dist/utils/redirects.d.ts +5 -0
  34. package/dist/utils/redirects.d.ts.map +1 -0
  35. package/dist/utils/redirects.js +16 -0
  36. package/dist/utils/redirects.js.map +1 -0
  37. package/dist/utils/state.d.ts +5 -0
  38. package/dist/utils/state.d.ts.map +1 -0
  39. package/dist/utils/state.js +7 -0
  40. package/dist/utils/state.js.map +1 -0
  41. package/dist/utils/urls.d.ts +5 -0
  42. package/dist/utils/urls.d.ts.map +1 -0
  43. package/dist/utils/urls.js +5 -0
  44. package/dist/utils/urls.js.map +1 -0
  45. package/package.json +7 -7
package/README.md CHANGED
@@ -12,7 +12,7 @@ Thus, the user collection in PayloadCMS becomes just a shadow of the information
12
12
  ## Install
13
13
 
14
14
  ```shell
15
- pnpm add payload-zitadel-plugin@0.3.9
15
+ pnpm add payload-zitadel-plugin@0.4.1
16
16
  ```
17
17
 
18
18
  ## Configuration
@@ -23,21 +23,20 @@ Initialize the plugin in Payload Config File. Change the parameters to connect t
23
23
 
24
24
  ```typescript
25
25
  import {buildConfig} from 'payload/config'
26
- import {ZitadelPlugin} from 'payload-zitadel-plugin'
26
+ import {zitadelPlugin} from 'payload-zitadel-plugin'
27
27
 
28
28
 
29
29
  export default buildConfig({
30
30
  ...,
31
31
  plugins: [
32
- ZitadelPlugin({
32
+ zitadelPlugin({
33
33
  // URL of your Zitadel instance
34
- issuerUrl: process.env.ZITADEL_URL,
34
+ issuerUrl: process.env.ZITADEL_URL ?? '',
35
35
 
36
- // in Zitadel create a new App->Web->PKCE, then copy the Client ID
37
- clientId: process.env.ZITADEL_CLIENT_ID,
38
-
39
- // interpolation text for the Login Button - "sign in with ..."
40
- label: 'Zitadel',
36
+ // in Zitadel create a new App->Web->PKCE in your project, then copy the Client ID
37
+ // DO NOT FORGET to add '{http://localhost with development mode on or https://your-domain.tld}/api/users/callback'
38
+ // to the allowed redirect URIs and ALSO to the post logout redirect URIs
39
+ clientId: process.env.ZITADEL_CLIENT_ID ?? '',
41
40
 
42
41
  // change field names, field labels and alse hide them if wanted
43
42
  /*
@@ -52,23 +51,40 @@ export default buildConfig({
52
51
  // set the name of the CustomStrategy in PayloadCMS - usually not necessary
53
52
  // strategyName: 'zitadel'
54
53
 
55
- // set to true if you do not want to use the Zitadel Profile Picture as the Avatar
56
- // disableAvatar: true
54
+ /*
55
+ avatar: {
56
+ // set to true if you do not want to use the Zitadel Profile Picture as the Avatar
57
+ disable: true
58
+ }
59
+ */
60
+
61
+
62
+ /*
63
+ loginButton: {
64
+ // set to true if you want to use your own custom login button
65
+ disable: true,
66
+
67
+ // interpolation text for the Login Button - "sign in with ..."
68
+ label: 'Zitadel'
69
+ }
70
+ */
57
71
 
58
- // set to true if you want to use your own custom login button
59
- // disableDefaultLoginButton: true
72
+ // if you want to manually control what happens after a successful login
73
+ // afterLogin: (req) => NextResponse.redirect('...')
60
74
 
61
- // if you want to manually control what happen after a successful login
62
- // state contains all URLSearchParams that were send to /authorize
63
- // onSuccess: (state) => NextResponse.redirect([serverURL, state.get('redirect')].join(''))
75
+ // if you want to manually control what happens after a successful logout
76
+ // afterLogout: (req) => NextResponse.redirect('...')
64
77
 
65
- // following properties are only needed if you want to authenticate clients for the API
66
- // if you are just using the CMS you can ignore all of them
67
- // in Zitadel create a new App->API->JWT
68
- // enableAPI: true,
69
- // apiClientId: process.env.ZITADEL_API_CLIENT_ID,
70
- // apiKeyId: process.env.ZITADEL_API_KEY_ID,
71
- // apiKey: process.env.ZITADEL_API_KEY
78
+ // following properties are only needed if you want to authenticate clients (e.g. a mobile app) for the API
79
+ // if the users are just visiting the CMS via a browser you can ignore all of them
80
+ // otherwise create in Zitadel a new App->API->JWT and copy the Client ID, Key ID and the Key itself
81
+ /*
82
+ api: {
83
+ clientId: process.env.ZITADEL_API_CLIENT_ID ?? ''
84
+ keyId: process.env.ZITADEL_API_KEY_ID ?? ''
85
+ key: process.env.ZITADEL_API_KEY ?? ''
86
+ }
87
+ */
72
88
  })
73
89
  ],
74
90
  ...
@@ -136,16 +152,32 @@ const nextConfig = {
136
152
  ]
137
153
  },
138
154
 
139
- // optional: enable auto-redirect to Zitadel login page if not logged in
140
155
  async redirects() {
141
156
  return [
157
+ // for proper logout
142
158
  {
143
- source: '/:path((?:admin|profile).*)',
159
+ source: '/:path((?!api).*)',
160
+ destination: '/api/users/end_session?redirect=/:path*',
161
+ has: [
162
+ {
163
+ type: 'cookie',
164
+ key: 'zitadel_logout'
165
+ }
166
+ ],
167
+ permanent: false
168
+ },
169
+ // optional: enable auto-redirect to Zitadel login page if not logged in
170
+ {
171
+ source: '/:path((?!admin\/logout)(?:admin|profile).*)',
144
172
  destination: '/api/users/authorize?redirect=/:path*',
145
173
  missing: [
146
174
  {
147
175
  type: 'cookie',
148
176
  key: 'zitadel_id_token'
177
+ },
178
+ {
179
+ type: 'cookie',
180
+ key: 'zitadel_logout'
149
181
  }
150
182
  ],
151
183
  permanent: false
@@ -1,4 +1,4 @@
1
1
  import React from 'react';
2
2
  import type { ZitadelLoginButtonProps } from '../../../types.js';
3
- export declare const LoginButton: ({ i18n, authorizeURL, label }: ZitadelLoginButtonProps) => Promise<React.JSX.Element>;
3
+ export declare const LoginButton: ({ payload: { config }, i18n, label }: ZitadelLoginButtonProps) => Promise<React.JSX.Element>;
4
4
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/components/server/LoginButton/index.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAEzB,OAAO,KAAK,EAAC,uBAAuB,EAAC,MAAM,mBAAmB,CAAA;AAE9D,eAAO,MAAM,WAAW,kCAAuC,uBAAuB,+BAK5E,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/components/server/LoginButton/index.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAGzB,OAAO,KAAK,EAAC,uBAAuB,EAAC,MAAM,mBAAmB,CAAA;AAG9D,eAAO,MAAM,WAAW,yCAA4C,uBAAuB,+BAKjF,CAAA"}
@@ -1,13 +1,15 @@
1
1
  import React from 'react';
2
2
  import { Button } from '@payloadcms/ui';
3
- export const LoginButton = async ({ i18n, authorizeURL, label })=>/*#__PURE__*/ React.createElement("div", {
3
+ import { ROUTES } from '../../../constants.js';
4
+ import { getAuthBaseURL } from '../../../utils/index.js';
5
+ export const LoginButton = async ({ payload: { config }, i18n, label })=>/*#__PURE__*/ React.createElement("div", {
4
6
  style: {
5
7
  display: 'flex',
6
8
  justifyContent: 'center'
7
9
  }
8
10
  }, /*#__PURE__*/ React.createElement(Button, {
9
11
  el: "anchor",
10
- url: authorizeURL
12
+ url: getAuthBaseURL(config) + ROUTES.authorize
11
13
  }, i18n.t('zitadelPlugin:signIn', {
12
14
  label
13
15
  })));
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/components/server/LoginButton/index.tsx"],"sourcesContent":["import React from 'react'\nimport {Button} from '@payloadcms/ui'\nimport type {ZitadelLoginButtonProps} from '../../../types.js'\n\nexport const LoginButton = async ({i18n, authorizeURL, label}: ZitadelLoginButtonProps) =>\n <div style={{display: 'flex', justifyContent: 'center'}}>\n <Button el=\"anchor\" url={authorizeURL}>\n {i18n.t('zitadelPlugin:signIn', {label})}\n </Button>\n </div>"],"names":["React","Button","LoginButton","i18n","authorizeURL","label","div","style","display","justifyContent","el","url","t"],"mappings":"AAAA,OAAOA,WAAW,QAAO;AACzB,SAAQC,MAAM,QAAO,iBAAgB;AAGrC,OAAO,MAAMC,cAAc,OAAO,EAACC,IAAI,EAAEC,YAAY,EAAEC,KAAK,EAA0B,iBAClF,oBAACC;QAAIC,OAAO;YAACC,SAAS;YAAQC,gBAAgB;QAAQ;qBAClD,oBAACR;QAAOS,IAAG;QAASC,KAAKP;OACpBD,KAAKS,CAAC,CAAC,wBAAwB;QAACP;IAAK,KAExC"}
1
+ {"version":3,"sources":["../../../../src/components/server/LoginButton/index.tsx"],"sourcesContent":["import React from 'react'\nimport {Button} from '@payloadcms/ui'\nimport {ROUTES} from '../../../constants.js'\nimport type {ZitadelLoginButtonProps} from '../../../types.js'\nimport {getAuthBaseURL} from '../../../utils/index.js'\n\nexport const LoginButton = async ({payload: {config}, i18n, label}: ZitadelLoginButtonProps) =>\n <div style={{display: 'flex', justifyContent: 'center'}}>\n <Button el=\"anchor\" url={getAuthBaseURL(config) + ROUTES.authorize}>\n {i18n.t('zitadelPlugin:signIn', {label})}\n </Button>\n </div>"],"names":["React","Button","ROUTES","getAuthBaseURL","LoginButton","payload","config","i18n","label","div","style","display","justifyContent","el","url","authorize","t"],"mappings":"AAAA,OAAOA,WAAW,QAAO;AACzB,SAAQC,MAAM,QAAO,iBAAgB;AACrC,SAAQC,MAAM,QAAO,wBAAuB;AAE5C,SAAQC,cAAc,QAAO,0BAAyB;AAEtD,OAAO,MAAMC,cAAc,OAAO,EAACC,SAAS,EAACC,MAAM,EAAC,EAAEC,IAAI,EAAEC,KAAK,EAA0B,iBACvF,oBAACC;QAAIC,OAAO;YAACC,SAAS;YAAQC,gBAAgB;QAAQ;qBAClD,oBAACX;QAAOY,IAAG;QAASC,KAAKX,eAAeG,UAAUJ,OAAOa,SAAS;OAC7DR,KAAKS,CAAC,CAAC,wBAAwB;QAACR;IAAK,KAExC"}
@@ -1,8 +1,32 @@
1
+ export declare const AUTHORIZE_QUERY: {
2
+ response_type: string;
3
+ scope: string;
4
+ code_challenge_method: string;
5
+ };
1
6
  export declare const COMPONENTS_PATH = "payload-zitadel-plugin/components";
2
7
  export declare const COOKIES: {
3
- pkce: string;
4
- idToken: string;
5
- state: string;
8
+ pkce: {
9
+ httpOnly: true;
10
+ path: string;
11
+ sameSite: "lax";
12
+ secure: boolean;
13
+ name: string;
14
+ };
15
+ idToken: {
16
+ httpOnly: true;
17
+ path: string;
18
+ sameSite: "lax";
19
+ secure: boolean;
20
+ name: string;
21
+ };
22
+ logout: {
23
+ httpOnly: true;
24
+ path: string;
25
+ sameSite: "lax";
26
+ secure: boolean;
27
+ name: string;
28
+ value: string;
29
+ };
6
30
  };
7
31
  export declare const DEFAULT_CONFIG: {
8
32
  fields: {
@@ -64,16 +88,16 @@ export declare const DEFAULT_CONFIG: {
64
88
  strategyName: string;
65
89
  label: string;
66
90
  };
67
- export declare const ERROR_MESSAGES: {
68
- issuerURL: string;
69
- clientId: string;
70
- apiClientId: string;
71
- apiKeyId: string;
72
- apiKey: string;
91
+ export declare const ENDPOINT_PATHS: {
92
+ authorize: string;
93
+ introspect: string;
94
+ token: string;
95
+ end_session: string;
73
96
  };
97
+ export declare const ROLES_KEY = "urn:zitadel:iam:org:project:roles";
74
98
  export declare const ROUTES: {
75
99
  authorize: string;
76
100
  callback: string;
77
- redirect: string;
101
+ end_session: string;
78
102
  };
79
103
  //# sourceMappingURL=constants.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,eAAe,sCAAsC,CAAA;AAElE,eAAO,MAAM,OAAO;;;;CAInB,CAAA;AAED,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAsC1B,CAAA;AAED,eAAO,MAAM,cAAc;;;;;;CAM1B,CAAA;AACD,eAAO,MAAM,MAAM;;;;CAIlB,CAAA"}
1
+ {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,eAAe;;;;CAI3B,CAAA;AAED,eAAO,MAAM,eAAe,sCAAsC,CAAA;AASlE,eAAO,MAAM,OAAO;;;;;;;;;;;;;;;;;;;;;;;CAcnB,CAAA;AAED,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAsC1B,CAAA;AAED,eAAO,MAAM,cAAc;;;;;CAK1B,CAAA;AAED,eAAO,MAAM,SAAS,sCAAsC,CAAA;AAE5D,eAAO,MAAM,MAAM;;;;CAIlB,CAAA"}
package/dist/constants.js CHANGED
@@ -1,8 +1,29 @@
1
+ export const AUTHORIZE_QUERY = {
2
+ response_type: 'code',
3
+ scope: 'openid email profile',
4
+ code_challenge_method: 'S256'
5
+ };
1
6
  export const COMPONENTS_PATH = 'payload-zitadel-plugin/components';
7
+ const COOKIE_CONFIG = {
8
+ httpOnly: true,
9
+ path: '/',
10
+ sameSite: 'lax',
11
+ secure: process.env.NODE_ENV == 'production'
12
+ };
2
13
  export const COOKIES = {
3
- pkce: 'zitadel_pkce_code_verifier',
4
- idToken: 'zitadel_id_token',
5
- state: 'zitadel_state'
14
+ pkce: {
15
+ name: 'zitadel_pkce_code_verifier',
16
+ ...COOKIE_CONFIG
17
+ },
18
+ idToken: {
19
+ name: 'zitadel_id_token',
20
+ ...COOKIE_CONFIG
21
+ },
22
+ logout: {
23
+ name: 'zitadel_logout',
24
+ value: 'true',
25
+ ...COOKIE_CONFIG
26
+ }
6
27
  };
7
28
  export const DEFAULT_CONFIG = {
8
29
  fields: {
@@ -64,17 +85,17 @@ export const DEFAULT_CONFIG = {
64
85
  strategyName: 'zitadel',
65
86
  label: 'Zitadel'
66
87
  };
67
- export const ERROR_MESSAGES = {
68
- issuerURL: 'ZITADEL-PLUGIN: ISSUER-URL IS EMPTY',
69
- clientId: 'ZITADEL-PLUGIN: CLIENT-ID IS EMPTY',
70
- apiClientId: 'ZITADEL-PLUGIN: API ENABLED, BUT API-CLIENT-ID IS EMPTY',
71
- apiKeyId: 'ZITADEL-PLUGIN: API ENABLED, BUT API-KEY-ID IS EMPTY',
72
- apiKey: 'ZITADEL-PLUGIN: API ENABLED, BUT API-KEY IS EMPTY'
88
+ export const ENDPOINT_PATHS = {
89
+ authorize: '/oauth/v2/authorize',
90
+ introspect: '/oauth/v2/introspect',
91
+ token: '/oauth/v2/token',
92
+ end_session: '/oidc/v1/end_session'
73
93
  };
94
+ export const ROLES_KEY = 'urn:zitadel:iam:org:project:roles';
74
95
  export const ROUTES = {
75
96
  authorize: '/authorize',
76
97
  callback: '/callback',
77
- redirect: '/redirect'
98
+ end_session: '/end_session'
78
99
  };
79
100
 
80
101
  //# sourceMappingURL=constants.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/constants.ts"],"sourcesContent":["import type {ZitadelFieldsConfig} from './types.js'\n\nexport const COMPONENTS_PATH = 'payload-zitadel-plugin/components'\n\nexport const COOKIES = {\n pkce: 'zitadel_pkce_code_verifier',\n idToken: 'zitadel_id_token',\n state: 'zitadel_state'\n}\n\nexport const DEFAULT_CONFIG = {\n fields: {\n id: {\n name: 'idp_id',\n label: {\n de: 'Identifikation im System des Identitätsanbieters',\n en: 'Identifier in the system of the Identity Provider'\n }\n },\n name: {\n name: 'name',\n label: {de: 'Name', en: 'Name'}\n },\n email: {\n name: 'email',\n label: {de: 'E-Mail', en: 'Email'}\n },\n image: {\n name: 'image',\n label: {de: 'Profilbild-URL', en: 'Profile picture URL'}\n },\n roles: {\n name: 'roles',\n label: {de: 'Rollen', en: 'Roles'},\n labels: {\n singular: {de: 'Rolle', en: 'Role'},\n plural: {de: 'Rollen', en: 'Roles'}\n }\n },\n roleFields: {\n name: {\n name: 'name',\n label: {de: 'Name', en: 'Name'}\n }\n }\n } satisfies ZitadelFieldsConfig,\n strategyName: 'zitadel',\n label: 'Zitadel'\n}\n\nexport const ERROR_MESSAGES = {\n issuerURL: 'ZITADEL-PLUGIN: ISSUER-URL IS EMPTY',\n clientId: 'ZITADEL-PLUGIN: CLIENT-ID IS EMPTY',\n apiClientId: 'ZITADEL-PLUGIN: API ENABLED, BUT API-CLIENT-ID IS EMPTY',\n apiKeyId: 'ZITADEL-PLUGIN: API ENABLED, BUT API-KEY-ID IS EMPTY',\n apiKey: 'ZITADEL-PLUGIN: API ENABLED, BUT API-KEY IS EMPTY'\n}\nexport const ROUTES = {\n authorize: '/authorize',\n callback: '/callback',\n redirect: '/redirect'\n}\n\n"],"names":["COMPONENTS_PATH","COOKIES","pkce","idToken","state","DEFAULT_CONFIG","fields","id","name","label","de","en","email","image","roles","labels","singular","plural","roleFields","strategyName","ERROR_MESSAGES","issuerURL","clientId","apiClientId","apiKeyId","apiKey","ROUTES","authorize","callback","redirect"],"mappings":"AAEA,OAAO,MAAMA,kBAAkB,oCAAmC;AAElE,OAAO,MAAMC,UAAU;IACnBC,MAAM;IACNC,SAAS;IACTC,OAAO;AACX,EAAC;AAED,OAAO,MAAMC,iBAAiB;IAC1BC,QAAQ;QACJC,IAAI;YACAC,MAAM;YACNC,OAAO;gBACHC,IAAI;gBACJC,IAAI;YACR;QACJ;QACAH,MAAM;YACFA,MAAM;YACNC,OAAO;gBAACC,IAAI;gBAAQC,IAAI;YAAM;QAClC;QACAC,OAAO;YACHJ,MAAM;YACNC,OAAO;gBAACC,IAAI;gBAAUC,IAAI;YAAO;QACrC;QACAE,OAAO;YACHL,MAAM;YACNC,OAAO;gBAACC,IAAI;gBAAkBC,IAAI;YAAqB;QAC3D;QACAG,OAAO;YACHN,MAAM;YACNC,OAAO;gBAACC,IAAI;gBAAUC,IAAI;YAAO;YACjCI,QAAQ;gBACJC,UAAU;oBAACN,IAAI;oBAASC,IAAI;gBAAM;gBAClCM,QAAQ;oBAACP,IAAI;oBAAUC,IAAI;gBAAO;YACtC;QACJ;QACAO,YAAY;YACRV,MAAM;gBACFA,MAAM;gBACNC,OAAO;oBAACC,IAAI;oBAAQC,IAAI;gBAAM;YAClC;QACJ;IACJ;IACAQ,cAAc;IACdV,OAAO;AACX,EAAC;AAED,OAAO,MAAMW,iBAAiB;IAC1BC,WAAW;IACXC,UAAU;IACVC,aAAa;IACbC,UAAU;IACVC,QAAQ;AACZ,EAAC;AACD,OAAO,MAAMC,SAAS;IAClBC,WAAW;IACXC,UAAU;IACVC,UAAU;AACd,EAAC"}
1
+ {"version":3,"sources":["../src/constants.ts"],"sourcesContent":["import {ResponseCookie} from 'next/dist/compiled/@edge-runtime/cookies/index.js'\nimport type {ZitadelFieldsConfig} from './types.js'\n\nexport const AUTHORIZE_QUERY = {\n response_type: 'code',\n scope: 'openid email profile',\n code_challenge_method: 'S256'\n}\n\nexport const COMPONENTS_PATH = 'payload-zitadel-plugin/components'\n\nconst COOKIE_CONFIG = {\n httpOnly: true,\n path: '/',\n sameSite: 'lax',\n secure: process.env.NODE_ENV == 'production'\n} satisfies Pick<ResponseCookie, 'httpOnly' | 'path' | 'sameSite' | 'secure'>\n\nexport const COOKIES = {\n pkce: {\n name: 'zitadel_pkce_code_verifier',\n ...COOKIE_CONFIG\n } satisfies Omit<ResponseCookie, 'value'>,\n idToken: {\n name: 'zitadel_id_token',\n ...COOKIE_CONFIG\n } satisfies Omit<ResponseCookie, 'value'>,\n logout: {\n name: 'zitadel_logout',\n value: 'true',\n ...COOKIE_CONFIG\n } satisfies ResponseCookie\n}\n\nexport const DEFAULT_CONFIG = {\n fields: {\n id: {\n name: 'idp_id',\n label: {\n de: 'Identifikation im System des Identitätsanbieters',\n en: 'Identifier in the system of the Identity Provider'\n }\n },\n name: {\n name: 'name',\n label: {de: 'Name', en: 'Name'}\n },\n email: {\n name: 'email',\n label: {de: 'E-Mail', en: 'Email'}\n },\n image: {\n name: 'image',\n label: {de: 'Profilbild-URL', en: 'Profile picture URL'}\n },\n roles: {\n name: 'roles',\n label: {de: 'Rollen', en: 'Roles'},\n labels: {\n singular: {de: 'Rolle', en: 'Role'},\n plural: {de: 'Rollen', en: 'Roles'}\n }\n },\n roleFields: {\n name: {\n name: 'name',\n label: {de: 'Name', en: 'Name'}\n }\n }\n } satisfies ZitadelFieldsConfig,\n strategyName: 'zitadel',\n label: 'Zitadel'\n}\n\nexport const ENDPOINT_PATHS = {\n authorize: '/oauth/v2/authorize',\n introspect: '/oauth/v2/introspect',\n token: '/oauth/v2/token',\n end_session: '/oidc/v1/end_session'\n}\n\nexport const ROLES_KEY = 'urn:zitadel:iam:org:project:roles'\n\nexport const ROUTES = {\n authorize: '/authorize',\n callback: '/callback',\n end_session: '/end_session'\n}\n\n"],"names":["AUTHORIZE_QUERY","response_type","scope","code_challenge_method","COMPONENTS_PATH","COOKIE_CONFIG","httpOnly","path","sameSite","secure","process","env","NODE_ENV","COOKIES","pkce","name","idToken","logout","value","DEFAULT_CONFIG","fields","id","label","de","en","email","image","roles","labels","singular","plural","roleFields","strategyName","ENDPOINT_PATHS","authorize","introspect","token","end_session","ROLES_KEY","ROUTES","callback"],"mappings":"AAGA,OAAO,MAAMA,kBAAkB;IAC3BC,eAAe;IACfC,OAAO;IACPC,uBAAuB;AAC3B,EAAC;AAED,OAAO,MAAMC,kBAAkB,oCAAmC;AAElE,MAAMC,gBAAgB;IAClBC,UAAU;IACVC,MAAM;IACNC,UAAU;IACVC,QAAQC,QAAQC,GAAG,CAACC,QAAQ,IAAI;AACpC;AAEA,OAAO,MAAMC,UAAU;IACnBC,MAAM;QACFC,MAAM;QACN,GAAGV,aAAa;IACpB;IACAW,SAAS;QACLD,MAAM;QACN,GAAGV,aAAa;IACpB;IACAY,QAAQ;QACJF,MAAM;QACNG,OAAO;QACP,GAAGb,aAAa;IACpB;AACJ,EAAC;AAED,OAAO,MAAMc,iBAAiB;IAC1BC,QAAQ;QACJC,IAAI;YACAN,MAAM;YACNO,OAAO;gBACHC,IAAI;gBACJC,IAAI;YACR;QACJ;QACAT,MAAM;YACFA,MAAM;YACNO,OAAO;gBAACC,IAAI;gBAAQC,IAAI;YAAM;QAClC;QACAC,OAAO;YACHV,MAAM;YACNO,OAAO;gBAACC,IAAI;gBAAUC,IAAI;YAAO;QACrC;QACAE,OAAO;YACHX,MAAM;YACNO,OAAO;gBAACC,IAAI;gBAAkBC,IAAI;YAAqB;QAC3D;QACAG,OAAO;YACHZ,MAAM;YACNO,OAAO;gBAACC,IAAI;gBAAUC,IAAI;YAAO;YACjCI,QAAQ;gBACJC,UAAU;oBAACN,IAAI;oBAASC,IAAI;gBAAM;gBAClCM,QAAQ;oBAACP,IAAI;oBAAUC,IAAI;gBAAO;YACtC;QACJ;QACAO,YAAY;YACRhB,MAAM;gBACFA,MAAM;gBACNO,OAAO;oBAACC,IAAI;oBAAQC,IAAI;gBAAM;YAClC;QACJ;IACJ;IACAQ,cAAc;IACdV,OAAO;AACX,EAAC;AAED,OAAO,MAAMW,iBAAiB;IAC1BC,WAAW;IACXC,YAAY;IACZC,OAAO;IACPC,aAAa;AACjB,EAAC;AAED,OAAO,MAAMC,YAAY,oCAAmC;AAE5D,OAAO,MAAMC,SAAS;IAClBL,WAAW;IACXM,UAAU;IACVH,aAAa;AACjB,EAAC"}
@@ -1,3 +1,3 @@
1
- import type { PayloadHandler } from 'payload';
2
- export declare const authorize: PayloadHandler;
1
+ import { ZitadelBaseHandler } from '../types.js';
2
+ export declare const authorize: ZitadelBaseHandler;
3
3
  //# sourceMappingURL=authorize.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"authorize.d.ts","sourceRoot":"","sources":["../../src/handlers/authorize.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,SAAS,CAAA;AAI3C,eAAO,MAAM,SAAS,EAAE,cA8BvB,CAAA"}
1
+ {"version":3,"file":"authorize.d.ts","sourceRoot":"","sources":["../../src/handlers/authorize.ts"],"names":[],"mappings":"AAEA,OAAO,EAAC,kBAAkB,EAAC,MAAM,aAAa,CAAA;AAG9C,eAAO,MAAM,SAAS,EAAE,kBAgBvB,CAAA"}
@@ -1,30 +1,22 @@
1
- import process from 'node:process';
2
1
  import { cookies } from 'next/headers.js';
3
- import { NextResponse } from 'next/server.js';
4
2
  import { COOKIES } from '../constants.js';
5
- export const authorize = async ({ searchParams, payload: { config } })=>{
6
- const { admin: { custom: { zitadel: { issuerURL, clientId, callbackURL } } } } = config;
7
- const code_verifier = Buffer.from(crypto.getRandomValues(new Uint8Array(24))).toString('base64url');
8
- const code_challenge = Buffer.from(await crypto.subtle.digest('SHA-256', new TextEncoder().encode(code_verifier))).toString('base64url');
9
- const cookieStore = await cookies();
10
- cookieStore.set({
11
- name: COOKIES.pkce,
12
- value: code_verifier,
13
- httpOnly: true,
14
- sameSite: 'lax',
15
- path: '/',
16
- maxAge: 300,
17
- secure: process.env.NODE_ENV == 'production'
18
- });
19
- return NextResponse.redirect(`${issuerURL}/oauth/v2/authorize?${new URLSearchParams({
20
- client_id: clientId,
21
- redirect_uri: callbackURL,
22
- response_type: 'code',
23
- scope: 'openid email profile',
24
- state: btoa(searchParams.toString()),
25
- code_challenge,
26
- code_challenge_method: 'S256'
27
- })}`);
28
- };
3
+ import { requestRedirect } from '../utils/index.js';
4
+ export const authorize = ({ issuerURL, clientId })=>async (req)=>{
5
+ const codeVerifier = Buffer.from(crypto.getRandomValues(new Uint8Array(24))).toString('base64url');
6
+ const codeChallenge = Buffer.from(await crypto.subtle.digest('SHA-256', new TextEncoder().encode(codeVerifier))).toString('base64url');
7
+ const cookieStore = await cookies();
8
+ cookieStore.set({
9
+ ...COOKIES.pkce,
10
+ value: codeVerifier,
11
+ maxAge: 300
12
+ });
13
+ return requestRedirect({
14
+ req,
15
+ issuerURL,
16
+ clientId,
17
+ invokedBy: 'authorize',
18
+ codeChallenge
19
+ });
20
+ };
29
21
 
30
22
  //# sourceMappingURL=authorize.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/handlers/authorize.ts"],"sourcesContent":["import process from 'node:process'\nimport {cookies} from 'next/headers.js'\nimport {NextResponse} from 'next/server.js'\nimport type {PayloadHandler} from 'payload'\nimport type {PayloadConfigWithZitadel} from '../types.js'\nimport {COOKIES} from '../constants.js'\n\nexport const authorize: PayloadHandler = async ({searchParams, payload: {config}}) => {\n\n const {admin: {custom: {zitadel: {issuerURL, clientId, callbackURL}}}} = config as PayloadConfigWithZitadel\n\n const code_verifier = Buffer.from(crypto.getRandomValues(new Uint8Array(24))).toString('base64url')\n\n const code_challenge = Buffer.from(await crypto.subtle.digest('SHA-256', new TextEncoder().encode(code_verifier))).toString('base64url')\n\n const cookieStore = await cookies()\n\n cookieStore.set({\n name: COOKIES.pkce,\n value: code_verifier,\n httpOnly: true,\n sameSite: 'lax',\n path: '/',\n maxAge: 300,\n secure: process.env.NODE_ENV == 'production'\n })\n\n return NextResponse.redirect(`${issuerURL}/oauth/v2/authorize?${new URLSearchParams({\n client_id: clientId,\n redirect_uri: callbackURL,\n response_type: 'code',\n scope: 'openid email profile',\n state: btoa(searchParams.toString()),\n code_challenge,\n code_challenge_method: 'S256'\n })}`)\n\n}\n"],"names":["process","cookies","NextResponse","COOKIES","authorize","searchParams","payload","config","admin","custom","zitadel","issuerURL","clientId","callbackURL","code_verifier","Buffer","from","crypto","getRandomValues","Uint8Array","toString","code_challenge","subtle","digest","TextEncoder","encode","cookieStore","set","name","pkce","value","httpOnly","sameSite","path","maxAge","secure","env","NODE_ENV","redirect","URLSearchParams","client_id","redirect_uri","response_type","scope","state","btoa","code_challenge_method"],"mappings":"AAAA,OAAOA,aAAa,eAAc;AAClC,SAAQC,OAAO,QAAO,kBAAiB;AACvC,SAAQC,YAAY,QAAO,iBAAgB;AAG3C,SAAQC,OAAO,QAAO,kBAAiB;AAEvC,OAAO,MAAMC,YAA4B,OAAO,EAACC,YAAY,EAAEC,SAAS,EAACC,MAAM,EAAC,EAAC;IAE7E,MAAM,EAACC,OAAO,EAACC,QAAQ,EAACC,SAAS,EAACC,SAAS,EAAEC,QAAQ,EAAEC,WAAW,EAAC,EAAC,EAAC,EAAC,GAAGN;IAEzE,MAAMO,gBAAgBC,OAAOC,IAAI,CAACC,OAAOC,eAAe,CAAC,IAAIC,WAAW,MAAMC,QAAQ,CAAC;IAEvF,MAAMC,iBAAiBN,OAAOC,IAAI,CAAC,MAAMC,OAAOK,MAAM,CAACC,MAAM,CAAC,WAAW,IAAIC,cAAcC,MAAM,CAACX,iBAAiBM,QAAQ,CAAC;IAE5H,MAAMM,cAAc,MAAMzB;IAE1ByB,YAAYC,GAAG,CAAC;QACZC,MAAMzB,QAAQ0B,IAAI;QAClBC,OAAOhB;QACPiB,UAAU;QACVC,UAAU;QACVC,MAAM;QACNC,QAAQ;QACRC,QAAQnC,QAAQoC,GAAG,CAACC,QAAQ,IAAI;IACpC;IAEA,OAAOnC,aAAaoC,QAAQ,CAAC,GAAG3B,UAAU,oBAAoB,EAAE,IAAI4B,gBAAgB;QAChFC,WAAW5B;QACX6B,cAAc5B;QACd6B,eAAe;QACfC,OAAO;QACPC,OAAOC,KAAKxC,aAAae,QAAQ;QACjCC;QACAyB,uBAAuB;IAC3B,IAAI;AAER,EAAC"}
1
+ {"version":3,"sources":["../../src/handlers/authorize.ts"],"sourcesContent":["import {cookies} from 'next/headers.js'\nimport {COOKIES} from '../constants.js'\nimport {ZitadelBaseHandler} from '../types.js'\nimport {requestRedirect} from '../utils/index.js'\n\nexport const authorize: ZitadelBaseHandler = ({issuerURL, clientId}) => async (req) => {\n\n const codeVerifier = Buffer.from(crypto.getRandomValues(new Uint8Array(24))).toString('base64url')\n\n const codeChallenge = Buffer.from(await crypto.subtle.digest('SHA-256', new TextEncoder().encode(codeVerifier))).toString('base64url')\n\n const cookieStore = await cookies()\n\n cookieStore.set({\n ...COOKIES.pkce,\n value: codeVerifier,\n maxAge: 300\n })\n\n return requestRedirect({req, issuerURL, clientId, invokedBy: 'authorize', codeChallenge})\n\n}\n"],"names":["cookies","COOKIES","requestRedirect","authorize","issuerURL","clientId","req","codeVerifier","Buffer","from","crypto","getRandomValues","Uint8Array","toString","codeChallenge","subtle","digest","TextEncoder","encode","cookieStore","set","pkce","value","maxAge","invokedBy"],"mappings":"AAAA,SAAQA,OAAO,QAAO,kBAAiB;AACvC,SAAQC,OAAO,QAAO,kBAAiB;AAEvC,SAAQC,eAAe,QAAO,oBAAmB;AAEjD,OAAO,MAAMC,YAAgC,CAAC,EAACC,SAAS,EAAEC,QAAQ,EAAC,GAAK,OAAOC;QAE3E,MAAMC,eAAeC,OAAOC,IAAI,CAACC,OAAOC,eAAe,CAAC,IAAIC,WAAW,MAAMC,QAAQ,CAAC;QAEtF,MAAMC,gBAAgBN,OAAOC,IAAI,CAAC,MAAMC,OAAOK,MAAM,CAACC,MAAM,CAAC,WAAW,IAAIC,cAAcC,MAAM,CAACX,gBAAgBM,QAAQ,CAAC;QAE1H,MAAMM,cAAc,MAAMnB;QAE1BmB,YAAYC,GAAG,CAAC;YACZ,GAAGnB,QAAQoB,IAAI;YACfC,OAAOf;YACPgB,QAAQ;QACZ;QAEA,OAAOrB,gBAAgB;YAACI;YAAKF;YAAWC;YAAUmB,WAAW;YAAaV;QAAa;IAE3F,EAAC"}
@@ -1,4 +1,3 @@
1
- import type { PayloadHandler } from 'payload';
2
- import type { ZitadelOnSuccess } from '../types.js';
3
- export declare const callback: (onSuccess: ZitadelOnSuccess) => PayloadHandler;
1
+ import { ZitadelCallbackHandler } from '../types.js';
2
+ export declare const callback: ZitadelCallbackHandler;
4
3
  //# sourceMappingURL=callback.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"callback.d.ts","sourceRoot":"","sources":["../../src/handlers/callback.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,SAAS,CAAA;AAC3C,OAAO,KAAK,EAA2C,gBAAgB,EAAC,MAAM,aAAa,CAAA;AAG3F,eAAO,MAAM,QAAQ,cAAe,gBAAgB,KAAG,cAoEtD,CAAA"}
1
+ {"version":3,"file":"callback.d.ts","sourceRoot":"","sources":["../../src/handlers/callback.ts"],"names":[],"mappings":"AAGA,OAAO,EAAC,sBAAsB,EAAuC,MAAM,aAAa,CAAA;AAGxF,eAAO,MAAM,QAAQ,EAAE,sBAiLtB,CAAA"}
@@ -1,53 +1,142 @@
1
- import process from 'node:process';
2
1
  import { SignJWT, decodeJwt } from 'jose';
3
2
  import { cookies } from 'next/headers.js';
4
- import { COOKIES } from '../constants.js';
5
- export const callback = (onSuccess)=>async ({ payload: { config, secret }, query: { code, state } })=>{
6
- const { admin: { custom: { zitadel: { issuerURL, clientId, callbackURL } } } } = config;
3
+ import { COOKIES, ENDPOINT_PATHS, ROLES_KEY, ROUTES } from '../constants.js';
4
+ import { getAuthBaseURL, getAuthSlug, getState } from '../utils/index.js';
5
+ export const callback = ({ issuerURL, clientId, fields, afterLogin, afterLogout })=>async (req)=>{
6
+ const { payload, query } = req;
7
+ const { config, secret } = payload;
8
+ const { code } = query;
9
+ const state = getState(req);
7
10
  const cookieStore = await cookies();
8
- const code_verifier = cookieStore.get(COOKIES.pkce)?.value;
9
- if (code_verifier) {
10
- const response = await fetch(new URL(`${issuerURL}/oauth/v2/token`), {
11
- method: 'POST',
12
- body: new URLSearchParams({
13
- grant_type: 'authorization_code',
14
- code: code,
15
- redirect_uri: callbackURL,
16
- client_id: clientId,
17
- code_verifier
18
- })
19
- });
20
- if (response.ok) {
21
- const { id_token } = await response.json();
22
- if (id_token) {
23
- cookieStore.delete(COOKIES.pkce);
24
- cookieStore.set({
25
- name: COOKIES.idToken,
26
- value: await new SignJWT(decodeJwt(id_token)).setProtectedHeader({
27
- alg: 'HS256'
28
- }).setIssuedAt().sign(new TextEncoder().encode(secret)),
29
- httpOnly: true,
30
- path: '/',
31
- sameSite: 'lax',
32
- maxAge: 900,
33
- secure: process.env.NODE_ENV == 'production'
34
- });
35
- return onSuccess(new URLSearchParams(atob(state ?? '')));
11
+ if (state.invokedBy == 'end_session') {
12
+ [
13
+ COOKIES.logout,
14
+ COOKIES.idToken
15
+ ].forEach((cookie)=>cookieStore.delete(cookie));
16
+ return afterLogout(req);
17
+ }
18
+ const codeVerifier = cookieStore.get(COOKIES.pkce.name)?.value;
19
+ if (!code) {
20
+ return Response.json({
21
+ status: 'error',
22
+ message: 'no code provided to verify'
23
+ });
24
+ }
25
+ if (!codeVerifier) {
26
+ return Response.json({
27
+ status: 'error',
28
+ message: 'code verifier not found (associated http-only cookie is empty)'
29
+ });
30
+ }
31
+ const tokenQueryData = {
32
+ grant_type: 'authorization_code',
33
+ code,
34
+ redirect_uri: getAuthBaseURL(config) + ROUTES.callback,
35
+ client_id: clientId,
36
+ code_verifier: codeVerifier
37
+ };
38
+ const tokenEndpoint = issuerURL + ENDPOINT_PATHS.token;
39
+ const tokenResponse = await fetch(new URL(tokenEndpoint), {
40
+ method: 'POST',
41
+ body: new URLSearchParams(tokenQueryData)
42
+ });
43
+ if (!tokenResponse.ok) {
44
+ return Response.json({
45
+ status: 'error',
46
+ message: 'error while communicating with token endpoint',
47
+ details: {
48
+ tokenEndpoint,
49
+ tokenQuery: tokenQueryData,
50
+ tokenResponseCode: `${tokenResponse.status} - ${tokenResponse.statusText}`
51
+ }
52
+ });
53
+ }
54
+ const tokenJson = await tokenResponse.json();
55
+ const { id_token: idToken } = tokenJson;
56
+ if (!idToken) {
57
+ return Response.json({
58
+ status: 'error',
59
+ message: 'token could not be retrieved from this response',
60
+ details: {
61
+ responseData: tokenJson
36
62
  }
37
- return Response.json({
38
- status: 'error',
39
- message: 'token could not be retrieved from the response'
63
+ });
64
+ }
65
+ let decodedIdToken;
66
+ try {
67
+ decodedIdToken = decodeJwt(idToken);
68
+ } catch (e) {
69
+ return Response.json({
70
+ status: 'error',
71
+ message: `error during decoding: ${JSON.stringify(e)}`,
72
+ details: {
73
+ idToken
74
+ }
75
+ });
76
+ }
77
+ const idpId = decodedIdToken.sub;
78
+ const userData = {
79
+ [fields.name.name]: decodedIdToken.name,
80
+ [fields.email.name]: decodedIdToken.email,
81
+ [fields.image.name]: decodedIdToken.picture,
82
+ [fields.roles.name]: Object.keys(decodedIdToken[ROLES_KEY] ?? {}).map((key)=>({
83
+ [fields.roleFields.name.name]: key
84
+ }))
85
+ };
86
+ if (!idpId) {
87
+ return Response.json({
88
+ status: 'error',
89
+ message: 'token is not complete (id not found)',
90
+ details: {
91
+ idToken,
92
+ decodedIdToken,
93
+ idpId
94
+ }
95
+ });
96
+ }
97
+ try {
98
+ const authSlug = getAuthSlug(config);
99
+ const { docs, totalDocs } = await payload.find({
100
+ collection: authSlug,
101
+ where: {
102
+ [fields.id.name]: {
103
+ equals: idpId
104
+ }
105
+ }
106
+ });
107
+ if (totalDocs) {
108
+ await payload.update({
109
+ collection: authSlug,
110
+ id: docs[0].id,
111
+ data: userData
112
+ });
113
+ } else {
114
+ await payload.create({
115
+ collection: authSlug,
116
+ data: {
117
+ [fields.id.name]: idpId,
118
+ ...userData
119
+ }
40
120
  });
41
121
  }
122
+ } catch (e) {
42
123
  return Response.json({
43
124
  status: 'error',
44
- message: 'error while communicating with token endpoint'
125
+ message: `error while creating/updating user: ${JSON.stringify(e)}`,
126
+ details: {
127
+ idpId
128
+ }
45
129
  });
46
130
  }
47
- return Response.json({
48
- status: 'error',
49
- message: 'code verifier not found (associated http-only cookie is empty)'
131
+ cookieStore.delete(COOKIES.pkce);
132
+ cookieStore.set({
133
+ ...COOKIES.idToken,
134
+ value: await new SignJWT(decodedIdToken).setProtectedHeader({
135
+ alg: 'HS256'
136
+ }).setIssuedAt().sign(new TextEncoder().encode(secret)),
137
+ maxAge: 900
50
138
  });
139
+ return afterLogin(req);
51
140
  };
52
141
 
53
142
  //# sourceMappingURL=callback.js.map