@wisemen/payload-core-auth 0.0.0 → 0.0.2
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.
- package/LICENSE.md +59 -0
- package/dist/authData-BieBPsYC.mjs +121 -0
- package/dist/authData-Cs0WZKng.d.mts +54 -0
- package/dist/client.d.mts +55 -0
- package/dist/client.mjs +141 -0
- package/dist/index.d.mts +73 -0
- package/dist/index.mjs +263 -0
- package/dist/payloadAuth.types-pFZl0rQd.d.mts +138 -0
- package/dist/server.d.mts +26 -0
- package/dist/server.mjs +3 -0
- package/dist/withPayloadTokenAuth-CthBSj8k.mjs +3 -0
- package/dist/withPayloadTokenAuth-DzS8Ryke.mjs +62 -0
- package/package.json +64 -2
package/LICENSE.md
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# PolyForm Strict License 1.0.0
|
|
2
|
+
|
|
3
|
+
<https://polyformproject.org/licenses/strict/1.0.0>
|
|
4
|
+
|
|
5
|
+
## Acceptance
|
|
6
|
+
|
|
7
|
+
In order to get any license under these terms, you must agree to them as both strict obligations and conditions to all your licenses.
|
|
8
|
+
|
|
9
|
+
## Copyright License
|
|
10
|
+
|
|
11
|
+
The licensor grants you a copyright license for the software to do everything you might do with the software that would otherwise infringe the licensor's copyright in it for any permitted purpose, other than distributing the software or making changes or new works based on the software.
|
|
12
|
+
|
|
13
|
+
## Patent License
|
|
14
|
+
|
|
15
|
+
The licensor grants you a patent license for the software that covers patent claims the licensor can license, or becomes able to license, that you would infringe by using the software.
|
|
16
|
+
|
|
17
|
+
## Noncommercial Purposes
|
|
18
|
+
|
|
19
|
+
Any noncommercial purpose is a permitted purpose.
|
|
20
|
+
|
|
21
|
+
## Personal Uses
|
|
22
|
+
|
|
23
|
+
Personal use for research, experiment, and testing for the benefit of public knowledge, personal study, private entertainment, hobby projects, amateur pursuits, or religious observance, without any anticipated commercial application, is use for a permitted purpose.
|
|
24
|
+
|
|
25
|
+
## Noncommercial Organizations
|
|
26
|
+
|
|
27
|
+
Use by any charitable organization, educational institution, public research organization, public safety or health organization, environmental protection organization, or government institution is use for a permitted purpose regardless of the source of funding or obligations resulting from the funding.
|
|
28
|
+
|
|
29
|
+
## Fair Use
|
|
30
|
+
|
|
31
|
+
You may have "fair use" rights for the software under the law. These terms do not limit them.
|
|
32
|
+
|
|
33
|
+
## No Other Rights
|
|
34
|
+
|
|
35
|
+
These terms do not allow you to sublicense or transfer any of your licenses to anyone else, or prevent the licensor from granting licenses to anyone else. These terms do not imply any other licenses.
|
|
36
|
+
|
|
37
|
+
## Patent Defense
|
|
38
|
+
|
|
39
|
+
If you make any written claim that the software infringes or contributes to infringement of any patent, your patent license for the software granted under these terms ends immediately. If your company makes such a claim, your patent license ends immediately for work on behalf of your company.
|
|
40
|
+
|
|
41
|
+
## Violations
|
|
42
|
+
|
|
43
|
+
The first time you are notified in writing that you have violated any of these terms, or done anything with the software not covered by your licenses, your licenses can nonetheless continue if you come into full compliance with these terms, and take practical steps to correct past violations, within 32 days of receiving notice. Otherwise, all your licenses end immediately.
|
|
44
|
+
|
|
45
|
+
## No Liability
|
|
46
|
+
|
|
47
|
+
***As far as the law allows, the software comes as is, without any warranty or condition, and the licensor will not be liable to you for any damages arising out of these terms or the use or nature of the software, under any kind of legal claim.***
|
|
48
|
+
|
|
49
|
+
## Definitions
|
|
50
|
+
|
|
51
|
+
The **licensor** is the individual or entity offering these terms, and the **software** is the software the licensor makes available under these terms.
|
|
52
|
+
|
|
53
|
+
**You** refers to the individual or entity agreeing to these terms.
|
|
54
|
+
|
|
55
|
+
**Your company** is any legal entity, sole proprietorship, or other kind of organization that you work for, plus all organizations that have control over, are under the control of, or are under common control with that organization. **Control** means ownership of substantially all the assets of an entity, or the power to direct its management and policies by vote, contract, or otherwise. Control can be direct or indirect.
|
|
56
|
+
|
|
57
|
+
**Your licenses** are all the licenses granted to you for the software under these terms.
|
|
58
|
+
|
|
59
|
+
**Use** means anything you do with the software requiring one of your licenses.
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { deleteCookie, getCookie, setCookie } from "cookies-next";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
|
|
4
|
+
//#region src/providers/providerStrategy.ts
|
|
5
|
+
function getZitadelScopes(organizationId) {
|
|
6
|
+
return [
|
|
7
|
+
"openid",
|
|
8
|
+
"profile",
|
|
9
|
+
"email",
|
|
10
|
+
"offline_access",
|
|
11
|
+
`urn:zitadel:iam:org:id:${organizationId}`
|
|
12
|
+
];
|
|
13
|
+
}
|
|
14
|
+
async function parseAuthResponse(response, errorMessage) {
|
|
15
|
+
if (!response.ok) throw new Error(`${errorMessage}: ${await response.text()}`);
|
|
16
|
+
return response.json();
|
|
17
|
+
}
|
|
18
|
+
const zitadelClientStrategy = {
|
|
19
|
+
exchangeAuthorizationCode: async ({ clientId, organizationId, authBaseUrl, cmsBaseUrl, code, codeVerifier }) => parseAuthResponse(await fetch(`${authBaseUrl}/oauth/v2/token`, {
|
|
20
|
+
body: new URLSearchParams({
|
|
21
|
+
client_id: clientId,
|
|
22
|
+
code,
|
|
23
|
+
code_verifier: codeVerifier,
|
|
24
|
+
grant_type: "authorization_code",
|
|
25
|
+
redirect_uri: `${cmsBaseUrl}/auth/callback`,
|
|
26
|
+
scope: getZitadelScopes(organizationId).join(" ")
|
|
27
|
+
}),
|
|
28
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
29
|
+
method: "POST"
|
|
30
|
+
}), "Failed to exchange authorization code"),
|
|
31
|
+
getLoginUrl: ({ clientId, organizationId, authBaseUrl, cmsBaseUrl, codeChallenge, searchParams }) => {
|
|
32
|
+
searchParams.append("client_id", clientId);
|
|
33
|
+
searchParams.append("redirect_uri", `${cmsBaseUrl}/auth/callback`);
|
|
34
|
+
searchParams.append("response_type", "code");
|
|
35
|
+
searchParams.append("prompt", "login");
|
|
36
|
+
searchParams.append("scope", getZitadelScopes(organizationId).join(" "));
|
|
37
|
+
searchParams.append("code_challenge", codeChallenge);
|
|
38
|
+
searchParams.append("code_challenge_method", "S256");
|
|
39
|
+
return `${authBaseUrl}/oauth/v2/authorize?${searchParams.toString()}`;
|
|
40
|
+
},
|
|
41
|
+
refreshToken: async ({ clientId, authBaseUrl, refreshToken }) => parseAuthResponse(await fetch(`${authBaseUrl}/oauth/v2/token`, {
|
|
42
|
+
body: new URLSearchParams({
|
|
43
|
+
client_id: clientId,
|
|
44
|
+
grant_type: "refresh_token",
|
|
45
|
+
refresh_token: refreshToken
|
|
46
|
+
}),
|
|
47
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
48
|
+
method: "POST"
|
|
49
|
+
}), "Failed to refresh token")
|
|
50
|
+
};
|
|
51
|
+
function getAuthClientStrategy(provider) {
|
|
52
|
+
switch (provider) {
|
|
53
|
+
case "zitadel": return zitadelClientStrategy;
|
|
54
|
+
default: throw new Error(`Unsupported auth provider: ${provider}`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
//#endregion
|
|
59
|
+
//#region src/shared/authData.ts
|
|
60
|
+
const AUTH_COOKIE_NAME = "tokens";
|
|
61
|
+
const CODE_CHALLENGE_COOKIE_NAME = "code_challenge";
|
|
62
|
+
const CODE_VERIFIER_COOKIE_NAME = "code_verifier";
|
|
63
|
+
const EXPIRED_AUTH_COOKIE = `${AUTH_COOKIE_NAME}=; Max-Age=0; Path=/; Expires=Thu, 01 Jan 1970 00:00:00 GMT`;
|
|
64
|
+
const tokensSchema = z.object({
|
|
65
|
+
expires_at: z.number(),
|
|
66
|
+
access_token: z.string(),
|
|
67
|
+
refresh_token: z.string(),
|
|
68
|
+
token_type: z.string()
|
|
69
|
+
});
|
|
70
|
+
function setAuthCookie(authResponse, context) {
|
|
71
|
+
const expiresAt = Date.now() + authResponse.expires_in * 1e3;
|
|
72
|
+
const tokens = {
|
|
73
|
+
expires_at: expiresAt,
|
|
74
|
+
access_token: authResponse.access_token,
|
|
75
|
+
refresh_token: authResponse.refresh_token,
|
|
76
|
+
token_type: authResponse.token_type
|
|
77
|
+
};
|
|
78
|
+
setCookie(AUTH_COOKIE_NAME, JSON.stringify(tokens), {
|
|
79
|
+
...context,
|
|
80
|
+
maxAge: authResponse.expires_in,
|
|
81
|
+
sameSite: "lax",
|
|
82
|
+
path: "/"
|
|
83
|
+
});
|
|
84
|
+
return {
|
|
85
|
+
expiresAt,
|
|
86
|
+
accessToken: authResponse.access_token,
|
|
87
|
+
refreshToken: authResponse.refresh_token
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
async function getAuthData({ req, res }) {
|
|
91
|
+
const tokensCookie = await getCookie(AUTH_COOKIE_NAME, {
|
|
92
|
+
req,
|
|
93
|
+
res
|
|
94
|
+
});
|
|
95
|
+
if (typeof tokensCookie !== "string") return null;
|
|
96
|
+
const parsedTokens = tokensSchema.safeParse(JSON.parse(tokensCookie));
|
|
97
|
+
if (!parsedTokens.success) return null;
|
|
98
|
+
return {
|
|
99
|
+
expiresAt: Number(parsedTokens.data.expires_at),
|
|
100
|
+
accessToken: parsedTokens.data.access_token,
|
|
101
|
+
refreshToken: parsedTokens.data.refresh_token
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
function removeAuthCookie(context) {
|
|
105
|
+
deleteCookie(AUTH_COOKIE_NAME, {
|
|
106
|
+
...context,
|
|
107
|
+
path: "/"
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
function removePkceCookies() {
|
|
111
|
+
deleteCookie(CODE_CHALLENGE_COOKIE_NAME, { path: "/" });
|
|
112
|
+
deleteCookie(CODE_VERIFIER_COOKIE_NAME, { path: "/" });
|
|
113
|
+
}
|
|
114
|
+
function getRemoveAuthCookieHeaders() {
|
|
115
|
+
const headers = new Headers();
|
|
116
|
+
headers.append("Set-Cookie", EXPIRED_AUTH_COOKIE);
|
|
117
|
+
return headers;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
//#endregion
|
|
121
|
+
export { getRemoveAuthCookieHeaders as a, setAuthCookie as c, getAuthData as i, tokensSchema as l, CODE_CHALLENGE_COOKIE_NAME as n, removeAuthCookie as o, CODE_VERIFIER_COOKIE_NAME as r, removePkceCookies as s, AUTH_COOKIE_NAME as t, getAuthClientStrategy as u };
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { n as AuthProviderType } from "./payloadAuth.types-pFZl0rQd.mjs";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { NextRequest, NextResponse } from "next/server";
|
|
4
|
+
|
|
5
|
+
//#region src/shared/authData.d.ts
|
|
6
|
+
declare const AUTH_COOKIE_NAME = "tokens";
|
|
7
|
+
declare const CODE_CHALLENGE_COOKIE_NAME = "code_challenge";
|
|
8
|
+
declare const CODE_VERIFIER_COOKIE_NAME = "code_verifier";
|
|
9
|
+
declare const tokensSchema: z.ZodObject<{
|
|
10
|
+
expires_at: z.ZodNumber;
|
|
11
|
+
access_token: z.ZodString;
|
|
12
|
+
refresh_token: z.ZodString;
|
|
13
|
+
token_type: z.ZodString;
|
|
14
|
+
}, z.core.$strip>;
|
|
15
|
+
interface AuthResponse {
|
|
16
|
+
access_token: string;
|
|
17
|
+
expires_in: number;
|
|
18
|
+
id_token?: string;
|
|
19
|
+
refresh_token: string;
|
|
20
|
+
token_type: string;
|
|
21
|
+
}
|
|
22
|
+
interface AuthData {
|
|
23
|
+
expiresAt: number;
|
|
24
|
+
accessToken: string;
|
|
25
|
+
refreshToken: string;
|
|
26
|
+
}
|
|
27
|
+
interface AuthClientConfig {
|
|
28
|
+
clientId: string;
|
|
29
|
+
organizationId: string;
|
|
30
|
+
authBaseUrl: string;
|
|
31
|
+
cmsBaseUrl: string;
|
|
32
|
+
provider: AuthProviderType;
|
|
33
|
+
}
|
|
34
|
+
interface CookieContext {
|
|
35
|
+
req?: NextRequest;
|
|
36
|
+
res?: NextResponse;
|
|
37
|
+
}
|
|
38
|
+
declare function setAuthCookie(authResponse: AuthResponse, context?: CookieContext): {
|
|
39
|
+
expiresAt: number;
|
|
40
|
+
accessToken: string;
|
|
41
|
+
refreshToken: string;
|
|
42
|
+
};
|
|
43
|
+
declare function getAuthData({
|
|
44
|
+
req,
|
|
45
|
+
res
|
|
46
|
+
}: {
|
|
47
|
+
req: NextRequest;
|
|
48
|
+
res: NextResponse;
|
|
49
|
+
}): Promise<AuthData | null>;
|
|
50
|
+
declare function removeAuthCookie(context?: CookieContext): void;
|
|
51
|
+
declare function removePkceCookies(): void;
|
|
52
|
+
declare function getRemoveAuthCookieHeaders(): Headers;
|
|
53
|
+
//#endregion
|
|
54
|
+
export { CODE_CHALLENGE_COOKIE_NAME as a, getAuthData as c, removePkceCookies as d, setAuthCookie as f, AuthResponse as i, getRemoveAuthCookieHeaders as l, AuthClientConfig as n, CODE_VERIFIER_COOKIE_NAME as o, tokensSchema as p, AuthData as r, CookieContext as s, AUTH_COOKIE_NAME as t, removeAuthCookie as u };
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { n as AuthProviderType } from "./payloadAuth.types-pFZl0rQd.mjs";
|
|
2
|
+
import { n as AuthClientConfig } from "./authData-Cs0WZKng.mjs";
|
|
3
|
+
import * as react0 from "react";
|
|
4
|
+
import React from "react";
|
|
5
|
+
|
|
6
|
+
//#region src/client/components/CallbackView.d.ts
|
|
7
|
+
interface CallbackViewProps {
|
|
8
|
+
clientId: string;
|
|
9
|
+
organizationId: string;
|
|
10
|
+
authBaseUrl: string;
|
|
11
|
+
cmsBaseUrl: string;
|
|
12
|
+
provider: AuthProviderType;
|
|
13
|
+
}
|
|
14
|
+
declare function CallbackView({
|
|
15
|
+
clientId,
|
|
16
|
+
organizationId,
|
|
17
|
+
authBaseUrl,
|
|
18
|
+
cmsBaseUrl,
|
|
19
|
+
provider
|
|
20
|
+
}: CallbackViewProps): react0.JSX.Element;
|
|
21
|
+
//#endregion
|
|
22
|
+
//#region src/client/components/LoginButton.d.ts
|
|
23
|
+
interface LoginButtonProps extends AuthClientConfig {
|
|
24
|
+
label?: string;
|
|
25
|
+
}
|
|
26
|
+
declare function LoginButton({
|
|
27
|
+
clientId,
|
|
28
|
+
organizationId,
|
|
29
|
+
authBaseUrl,
|
|
30
|
+
cmsBaseUrl,
|
|
31
|
+
label,
|
|
32
|
+
provider
|
|
33
|
+
}: LoginButtonProps): React.JSX.Element;
|
|
34
|
+
//#endregion
|
|
35
|
+
//#region src/client/loginWithCode.d.ts
|
|
36
|
+
interface LoginWithCodeParams extends AuthClientConfig {
|
|
37
|
+
code: string;
|
|
38
|
+
codeVerifier: string;
|
|
39
|
+
}
|
|
40
|
+
declare function loginWithCode({
|
|
41
|
+
clientId,
|
|
42
|
+
organizationId,
|
|
43
|
+
authBaseUrl,
|
|
44
|
+
cmsBaseUrl,
|
|
45
|
+
code,
|
|
46
|
+
codeVerifier,
|
|
47
|
+
provider
|
|
48
|
+
}: LoginWithCodeParams): Promise<{
|
|
49
|
+
success: boolean;
|
|
50
|
+
}>;
|
|
51
|
+
//#endregion
|
|
52
|
+
//#region src/client/logout.d.ts
|
|
53
|
+
declare function logout(): void;
|
|
54
|
+
//#endregion
|
|
55
|
+
export { CallbackView, LoginButton, loginWithCode, logout };
|
package/dist/client.mjs
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { c as setAuthCookie, n as CODE_CHALLENGE_COOKIE_NAME, o as removeAuthCookie, r as CODE_VERIFIER_COOKIE_NAME, s as removePkceCookies, u as getAuthClientStrategy } from "./authData-BieBPsYC.mjs";
|
|
4
|
+
import { getCookie } from "cookies-next";
|
|
5
|
+
import { LoadingOverlay } from "@payloadcms/ui";
|
|
6
|
+
import { redirect, useRouter, useSearchParams } from "next/navigation";
|
|
7
|
+
import React, { useEffect, useState } from "react";
|
|
8
|
+
import { jsx } from "react/jsx-runtime";
|
|
9
|
+
import { setCookie as setCookie$1 } from "cookies-next/client";
|
|
10
|
+
import pkceChallenge from "pkce-challenge";
|
|
11
|
+
|
|
12
|
+
//#region src/client/loginWithCode.ts
|
|
13
|
+
async function loginWithCode({ clientId, organizationId, authBaseUrl, cmsBaseUrl, code, codeVerifier, provider }) {
|
|
14
|
+
if (!codeVerifier) throw new Error("Code verifier not found");
|
|
15
|
+
setAuthCookie(await getAuthClientStrategy(provider).exchangeAuthorizationCode({
|
|
16
|
+
clientId,
|
|
17
|
+
organizationId,
|
|
18
|
+
authBaseUrl,
|
|
19
|
+
cmsBaseUrl,
|
|
20
|
+
code,
|
|
21
|
+
codeVerifier
|
|
22
|
+
}));
|
|
23
|
+
removePkceCookies();
|
|
24
|
+
return { success: true };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
//#endregion
|
|
28
|
+
//#region src/client/components/CallbackView.tsx
|
|
29
|
+
function CallbackView({ clientId, organizationId, authBaseUrl, cmsBaseUrl, provider }) {
|
|
30
|
+
const code = useSearchParams()?.get("code");
|
|
31
|
+
const router = useRouter();
|
|
32
|
+
if (code == null) redirect("/login");
|
|
33
|
+
const codeVerifier = getCookie(CODE_VERIFIER_COOKIE_NAME);
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
loginWithCode({
|
|
36
|
+
clientId,
|
|
37
|
+
organizationId,
|
|
38
|
+
authBaseUrl,
|
|
39
|
+
cmsBaseUrl,
|
|
40
|
+
code,
|
|
41
|
+
codeVerifier,
|
|
42
|
+
provider
|
|
43
|
+
}).then(({ success }) => {
|
|
44
|
+
if (success) {
|
|
45
|
+
router.push("/");
|
|
46
|
+
window.location.replace("/");
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
}, [
|
|
50
|
+
authBaseUrl,
|
|
51
|
+
clientId,
|
|
52
|
+
cmsBaseUrl,
|
|
53
|
+
code,
|
|
54
|
+
codeVerifier,
|
|
55
|
+
organizationId,
|
|
56
|
+
provider,
|
|
57
|
+
router
|
|
58
|
+
]);
|
|
59
|
+
return /* @__PURE__ */ jsx("div", {
|
|
60
|
+
style: {
|
|
61
|
+
alignItems: "center",
|
|
62
|
+
display: "flex",
|
|
63
|
+
flexDirection: "column",
|
|
64
|
+
gap: "2rem",
|
|
65
|
+
height: "100vh",
|
|
66
|
+
justifyContent: "center"
|
|
67
|
+
},
|
|
68
|
+
children: /* @__PURE__ */ jsx(LoadingOverlay, {})
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
//#endregion
|
|
73
|
+
//#region src/client/components/LoginButton.tsx
|
|
74
|
+
async function getLoginUrl({ clientId, organizationId, authBaseUrl, cmsBaseUrl, provider }) {
|
|
75
|
+
const searchParams = new URLSearchParams();
|
|
76
|
+
let codeChallenge = await getCookie(CODE_CHALLENGE_COOKIE_NAME);
|
|
77
|
+
const codeVerifier = await getCookie(CODE_VERIFIER_COOKIE_NAME);
|
|
78
|
+
if (codeChallenge == null || codeVerifier == null) {
|
|
79
|
+
const codes = await pkceChallenge();
|
|
80
|
+
setCookie$1(CODE_CHALLENGE_COOKIE_NAME, codes.code_challenge, {
|
|
81
|
+
sameSite: "lax",
|
|
82
|
+
path: "/"
|
|
83
|
+
});
|
|
84
|
+
setCookie$1(CODE_VERIFIER_COOKIE_NAME, codes.code_verifier, {
|
|
85
|
+
sameSite: "lax",
|
|
86
|
+
path: "/"
|
|
87
|
+
});
|
|
88
|
+
codeChallenge = codes.code_challenge;
|
|
89
|
+
}
|
|
90
|
+
return getAuthClientStrategy(provider).getLoginUrl({
|
|
91
|
+
clientId,
|
|
92
|
+
organizationId,
|
|
93
|
+
authBaseUrl,
|
|
94
|
+
cmsBaseUrl,
|
|
95
|
+
codeChallenge: String(codeChallenge),
|
|
96
|
+
searchParams
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
function LoginButton({ clientId, organizationId, authBaseUrl, cmsBaseUrl, label = "Sign in", provider }) {
|
|
100
|
+
const [url, setUrl] = useState(null);
|
|
101
|
+
useEffect(() => {
|
|
102
|
+
getLoginUrl({
|
|
103
|
+
clientId,
|
|
104
|
+
organizationId,
|
|
105
|
+
authBaseUrl,
|
|
106
|
+
cmsBaseUrl,
|
|
107
|
+
label,
|
|
108
|
+
provider
|
|
109
|
+
}).then((value) => {
|
|
110
|
+
setUrl(value);
|
|
111
|
+
});
|
|
112
|
+
}, [
|
|
113
|
+
authBaseUrl,
|
|
114
|
+
clientId,
|
|
115
|
+
cmsBaseUrl,
|
|
116
|
+
label,
|
|
117
|
+
organizationId,
|
|
118
|
+
provider
|
|
119
|
+
]);
|
|
120
|
+
return /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx("a", {
|
|
121
|
+
className: `
|
|
122
|
+
btn btn--style-primary btn--icon-style-without-border btn--size-medium
|
|
123
|
+
`,
|
|
124
|
+
style: {
|
|
125
|
+
display: "block",
|
|
126
|
+
textAlign: "center",
|
|
127
|
+
width: "100%"
|
|
128
|
+
},
|
|
129
|
+
href: url ?? "",
|
|
130
|
+
children: label
|
|
131
|
+
}) });
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
//#endregion
|
|
135
|
+
//#region src/client/logout.ts
|
|
136
|
+
function logout() {
|
|
137
|
+
removeAuthCookie();
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
//#endregion
|
|
141
|
+
export { CallbackView, LoginButton, loginWithCode, logout };
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { a as CreateAuthStrategyParams, c as CreatePayloadAuthPluginParams, d as CreateZitadelUserHookParams, f as PayloadAuthPluginResult, g as createPayloadCollectionAuth, i as BaseUserRecordWithId, l as CreatePayloadCollectionAuthParams, m as PublicAuthConfig, n as AuthProviderType, o as CreateAuthUserHookParams, p as PayloadAuthProvider, r as BaseUserRecord, s as CreateFirstUserConfig, t as AuthEnv, u as CreateZitadelAuthStrategyParams } from "./payloadAuth.types-pFZl0rQd.mjs";
|
|
2
|
+
import { a as CODE_CHALLENGE_COOKIE_NAME, c as getAuthData, d as removePkceCookies, f as setAuthCookie, i as AuthResponse, l as getRemoveAuthCookieHeaders, n as AuthClientConfig, o as CODE_VERIFIER_COOKIE_NAME, p as tokensSchema, r as AuthData, s as CookieContext, t as AUTH_COOKIE_NAME, u as removeAuthCookie } from "./authData-Cs0WZKng.mjs";
|
|
3
|
+
import { AuthStrategy, AuthStrategyResult, CollectionAfterChangeHook, TypeWithID } from "payload";
|
|
4
|
+
|
|
5
|
+
//#region src/plugin/payloadAuthPlugin.d.ts
|
|
6
|
+
declare function createPayloadAuthPlugin<TUser extends BaseUserRecordWithId>({
|
|
7
|
+
isUserAllowed,
|
|
8
|
+
authConfig,
|
|
9
|
+
createFirstUser,
|
|
10
|
+
provider,
|
|
11
|
+
shouldSkipUserSync,
|
|
12
|
+
tenantCollectionSlug,
|
|
13
|
+
tokenRefreshBufferMs,
|
|
14
|
+
userCollectionSlug
|
|
15
|
+
}: CreatePayloadAuthPluginParams<TUser>): PayloadAuthPluginResult;
|
|
16
|
+
//#endregion
|
|
17
|
+
//#region src/providers/providerStrategy.d.ts
|
|
18
|
+
interface GetLoginUrlParams {
|
|
19
|
+
clientId: string;
|
|
20
|
+
organizationId: string;
|
|
21
|
+
authBaseUrl: string;
|
|
22
|
+
cmsBaseUrl: string;
|
|
23
|
+
codeChallenge: string;
|
|
24
|
+
searchParams: URLSearchParams;
|
|
25
|
+
}
|
|
26
|
+
interface ExchangeAuthorizationCodeParams {
|
|
27
|
+
clientId: string;
|
|
28
|
+
organizationId: string;
|
|
29
|
+
authBaseUrl: string;
|
|
30
|
+
cmsBaseUrl: string;
|
|
31
|
+
code: string;
|
|
32
|
+
codeVerifier: string;
|
|
33
|
+
}
|
|
34
|
+
interface RefreshProviderTokenParams {
|
|
35
|
+
clientId: string;
|
|
36
|
+
organizationId: string;
|
|
37
|
+
authBaseUrl: string;
|
|
38
|
+
refreshToken: string;
|
|
39
|
+
}
|
|
40
|
+
interface AuthClientStrategy {
|
|
41
|
+
exchangeAuthorizationCode: (params: ExchangeAuthorizationCodeParams) => Promise<AuthResponse>;
|
|
42
|
+
getLoginUrl: (params: GetLoginUrlParams) => string;
|
|
43
|
+
refreshToken: (params: RefreshProviderTokenParams) => Promise<AuthResponse>;
|
|
44
|
+
}
|
|
45
|
+
declare function getAuthClientStrategy(provider: AuthProviderType): AuthClientStrategy;
|
|
46
|
+
//#endregion
|
|
47
|
+
//#region src/providers/zitadel/zitadelProvider.d.ts
|
|
48
|
+
declare function createZitadelAuthProvider<TUser extends BaseUserRecord & TypeWithID>(env: AuthEnv): PayloadAuthProvider<TUser>;
|
|
49
|
+
//#endregion
|
|
50
|
+
//#region src/providers/zitadel/zitadelStrategy.d.ts
|
|
51
|
+
declare function createZitadelAuthStrategy<TUser extends BaseUserRecordWithId>({
|
|
52
|
+
isUserAllowed,
|
|
53
|
+
createFirstUser: createFirstUserConfig,
|
|
54
|
+
env,
|
|
55
|
+
userCollectionSlug
|
|
56
|
+
}: CreateZitadelAuthStrategyParams<TUser>): AuthStrategy;
|
|
57
|
+
//#endregion
|
|
58
|
+
//#region src/providers/zitadel/zitadelUserHook.d.ts
|
|
59
|
+
declare function createZitadelUserHook<TUser extends BaseUserRecordWithId>({
|
|
60
|
+
env,
|
|
61
|
+
shouldSkip
|
|
62
|
+
}: CreateZitadelUserHookParams<TUser>): CollectionAfterChangeHook<TUser>;
|
|
63
|
+
//#endregion
|
|
64
|
+
//#region src/shared/constants.d.ts
|
|
65
|
+
declare const DEFAULT_ACCESS_TOKEN_EXPIRATION: number;
|
|
66
|
+
declare const DEFAULT_MAX_LOGIN_ATTEMPTS = 5;
|
|
67
|
+
declare const DEFAULT_LOCK_TIME: number;
|
|
68
|
+
//#endregion
|
|
69
|
+
//#region src/shared/payloadAuth.shared.d.ts
|
|
70
|
+
declare const USER_NOT_AUTHENTICATED: AuthStrategyResult;
|
|
71
|
+
declare function getUnauthenticatedResult(clearAuth: boolean): AuthStrategyResult;
|
|
72
|
+
//#endregion
|
|
73
|
+
export { AUTH_COOKIE_NAME, AuthClientConfig, AuthData, AuthEnv, AuthProviderType, AuthResponse, BaseUserRecord, BaseUserRecordWithId, CODE_CHALLENGE_COOKIE_NAME, CODE_VERIFIER_COOKIE_NAME, CookieContext, CreateAuthStrategyParams, CreateAuthUserHookParams, CreateFirstUserConfig, CreatePayloadAuthPluginParams, CreatePayloadCollectionAuthParams, CreateZitadelAuthStrategyParams, CreateZitadelUserHookParams, DEFAULT_ACCESS_TOKEN_EXPIRATION, DEFAULT_LOCK_TIME, DEFAULT_MAX_LOGIN_ATTEMPTS, PayloadAuthPluginResult, PayloadAuthProvider, PublicAuthConfig, USER_NOT_AUTHENTICATED, createPayloadAuthPlugin, createPayloadCollectionAuth, createZitadelAuthProvider, createZitadelAuthStrategy, createZitadelUserHook, getAuthClientStrategy, getAuthData, getRemoveAuthCookieHeaders, getUnauthenticatedResult, removeAuthCookie, removePkceCookies, setAuthCookie, tokensSchema };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
import { a as getRemoveAuthCookieHeaders, c as setAuthCookie, i as getAuthData, l as tokensSchema, n as CODE_CHALLENGE_COOKIE_NAME, o as removeAuthCookie, r as CODE_VERIFIER_COOKIE_NAME, s as removePkceCookies, t as AUTH_COOKIE_NAME, u as getAuthClientStrategy } from "./authData-BieBPsYC.mjs";
|
|
2
|
+
import { getPayload } from "@wisemen/payload-core-utils";
|
|
3
|
+
import { createRemoteJWKSet, errors, jwtVerify } from "jose";
|
|
4
|
+
|
|
5
|
+
//#region src/shared/constants.ts
|
|
6
|
+
const DEFAULT_ACCESS_TOKEN_EXPIRATION = 3600 * 24 * 30;
|
|
7
|
+
const DEFAULT_MAX_LOGIN_ATTEMPTS = 5;
|
|
8
|
+
const DEFAULT_LOCK_TIME = 600 * 1e3;
|
|
9
|
+
|
|
10
|
+
//#endregion
|
|
11
|
+
//#region src/plugin/payloadCollectionAuth.ts
|
|
12
|
+
function createPayloadCollectionAuth({ lockTime = DEFAULT_LOCK_TIME, maxLoginAttempts = DEFAULT_MAX_LOGIN_ATTEMPTS, strategy, tokenExpiration = DEFAULT_ACCESS_TOKEN_EXPIRATION, verify = false }) {
|
|
13
|
+
return {
|
|
14
|
+
lockTime,
|
|
15
|
+
disableLocalStrategy: true,
|
|
16
|
+
maxLoginAttempts,
|
|
17
|
+
strategies: [strategy],
|
|
18
|
+
tokenExpiration,
|
|
19
|
+
verify
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
//#endregion
|
|
24
|
+
//#region src/plugin/payloadAuthPlugin.ts
|
|
25
|
+
function mergePublicAuthConfig(publicAuth, overrides) {
|
|
26
|
+
return {
|
|
27
|
+
clientId: overrides?.authClientId ?? publicAuth.authClientId,
|
|
28
|
+
organizationId: overrides?.authOrganizationId ?? publicAuth.authOrganizationId,
|
|
29
|
+
authBaseUrl: overrides?.authBaseUrl ?? publicAuth.authBaseUrl,
|
|
30
|
+
cmsBaseUrl: overrides?.cmsBaseUrl ?? publicAuth.cmsBaseUrl,
|
|
31
|
+
provider: overrides?.provider ?? publicAuth.provider
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
function createPayloadAuthPlugin({ isUserAllowed, authConfig, createFirstUser: createFirstUser$1, provider, shouldSkipUserSync, tenantCollectionSlug, tokenRefreshBufferMs, userCollectionSlug }) {
|
|
35
|
+
const strategy = provider.createStrategy({
|
|
36
|
+
isUserAllowed,
|
|
37
|
+
createFirstUser: createFirstUser$1 ?? {
|
|
38
|
+
tenantCollectionSlug,
|
|
39
|
+
userData: ({ email }) => ({ email })
|
|
40
|
+
},
|
|
41
|
+
userCollectionSlug
|
|
42
|
+
});
|
|
43
|
+
const userHook = provider.createUserHook({ shouldSkip: shouldSkipUserSync });
|
|
44
|
+
const publicAuth = provider.getPublicConfig();
|
|
45
|
+
return {
|
|
46
|
+
auth: publicAuth,
|
|
47
|
+
createCollectionAuth: (overrides) => createPayloadCollectionAuth({
|
|
48
|
+
...authConfig,
|
|
49
|
+
...overrides,
|
|
50
|
+
strategy
|
|
51
|
+
}),
|
|
52
|
+
getCallbackViewProps: (overrides) => mergePublicAuthConfig(publicAuth, overrides),
|
|
53
|
+
getLoginButtonProps: (overrides) => mergePublicAuthConfig(publicAuth, overrides),
|
|
54
|
+
middleware: async (middlewareState) => {
|
|
55
|
+
const { withPayloadTokenAuth } = await import("./withPayloadTokenAuth-CthBSj8k.mjs");
|
|
56
|
+
return withPayloadTokenAuth(middlewareState, {
|
|
57
|
+
auth: provider.getMiddlewareAuthConfig(),
|
|
58
|
+
tokenRefreshBufferMs
|
|
59
|
+
});
|
|
60
|
+
},
|
|
61
|
+
plugin: () => (incomingConfig) => incomingConfig,
|
|
62
|
+
strategy,
|
|
63
|
+
userHook
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
//#endregion
|
|
68
|
+
//#region src/shared/payloadAuth.shared.ts
|
|
69
|
+
const USER_NOT_AUTHENTICATED = { user: null };
|
|
70
|
+
function getUnauthenticatedResult(clearAuth) {
|
|
71
|
+
if (!clearAuth) return USER_NOT_AUTHENTICATED;
|
|
72
|
+
return {
|
|
73
|
+
responseHeaders: getRemoveAuthCookieHeaders(),
|
|
74
|
+
user: null
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
//#endregion
|
|
79
|
+
//#region src/providers/zitadel/zitadelStrategy.ts
|
|
80
|
+
async function findUserByEmail({ userCollectionSlug, userEmail }) {
|
|
81
|
+
return (await (await getPayload()).find({
|
|
82
|
+
collection: userCollectionSlug,
|
|
83
|
+
limit: 1,
|
|
84
|
+
overrideAccess: true,
|
|
85
|
+
where: { email: { equals: userEmail } }
|
|
86
|
+
})).docs[0];
|
|
87
|
+
}
|
|
88
|
+
async function createFirstUser({ createFirstUserConfig, userCollectionSlug, userEmail }) {
|
|
89
|
+
const payload = await getPayload();
|
|
90
|
+
if (createFirstUserConfig == null) return USER_NOT_AUTHENTICATED;
|
|
91
|
+
const existingUserWithEmail = await findUserByEmail({
|
|
92
|
+
userCollectionSlug,
|
|
93
|
+
userEmail
|
|
94
|
+
});
|
|
95
|
+
if (existingUserWithEmail != null) return { user: {
|
|
96
|
+
...existingUserWithEmail,
|
|
97
|
+
collection: userCollectionSlug
|
|
98
|
+
} };
|
|
99
|
+
if ((await payload.find({
|
|
100
|
+
collection: userCollectionSlug,
|
|
101
|
+
limit: 1,
|
|
102
|
+
overrideAccess: true
|
|
103
|
+
})).docs.length > 0) return USER_NOT_AUTHENTICATED;
|
|
104
|
+
let existingTenant = (await payload.find({
|
|
105
|
+
collection: createFirstUserConfig.tenantCollectionSlug,
|
|
106
|
+
limit: 1,
|
|
107
|
+
overrideAccess: true
|
|
108
|
+
})).docs[0];
|
|
109
|
+
existingTenant ??= await payload.create({
|
|
110
|
+
collection: createFirstUserConfig.tenantCollectionSlug,
|
|
111
|
+
data: createFirstUserConfig.createTenantData?.() ?? { title: "Default" },
|
|
112
|
+
overrideAccess: true
|
|
113
|
+
});
|
|
114
|
+
if (existingTenant == null) throw new Error("Failed to resolve tenant for first user creation");
|
|
115
|
+
try {
|
|
116
|
+
return { user: {
|
|
117
|
+
...await payload.create({
|
|
118
|
+
collection: userCollectionSlug,
|
|
119
|
+
data: createFirstUserConfig.userData({
|
|
120
|
+
tenantId: existingTenant.id,
|
|
121
|
+
isFirstUser: true,
|
|
122
|
+
email: userEmail
|
|
123
|
+
}),
|
|
124
|
+
draft: false,
|
|
125
|
+
overrideAccess: true
|
|
126
|
+
}),
|
|
127
|
+
collection: userCollectionSlug
|
|
128
|
+
} };
|
|
129
|
+
} catch (error) {
|
|
130
|
+
const userCreatedConcurrently = await findUserByEmail({
|
|
131
|
+
userCollectionSlug,
|
|
132
|
+
userEmail
|
|
133
|
+
});
|
|
134
|
+
if (userCreatedConcurrently != null) return { user: {
|
|
135
|
+
...userCreatedConcurrently,
|
|
136
|
+
collection: userCollectionSlug
|
|
137
|
+
} };
|
|
138
|
+
throw error;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
function createZitadelAuthStrategy({ isUserAllowed, createFirstUser: createFirstUserConfig, env, userCollectionSlug }) {
|
|
142
|
+
return {
|
|
143
|
+
name: "zitadel",
|
|
144
|
+
authenticate: async (ctx) => {
|
|
145
|
+
const bearerToken = ctx.headers.get("Authorization")?.split(" ")[1];
|
|
146
|
+
if (bearerToken == null) return USER_NOT_AUTHENTICATED;
|
|
147
|
+
const jwk = createRemoteJWKSet(new URL(env.authJwksEndpoint));
|
|
148
|
+
try {
|
|
149
|
+
const userEmail = (await jwtVerify(bearerToken, jwk, {
|
|
150
|
+
issuer: env.authIssuer,
|
|
151
|
+
audience: env.authClientId
|
|
152
|
+
})).payload.email;
|
|
153
|
+
if (typeof userEmail !== "string" || userEmail.length === 0) return USER_NOT_AUTHENTICATED;
|
|
154
|
+
const singleUser = await findUserByEmail({
|
|
155
|
+
userCollectionSlug,
|
|
156
|
+
userEmail
|
|
157
|
+
});
|
|
158
|
+
if (singleUser == null) return createFirstUser({
|
|
159
|
+
createFirstUserConfig,
|
|
160
|
+
userCollectionSlug,
|
|
161
|
+
userEmail
|
|
162
|
+
});
|
|
163
|
+
if (!isUserAllowed(singleUser)) return USER_NOT_AUTHENTICATED;
|
|
164
|
+
return { user: {
|
|
165
|
+
...singleUser,
|
|
166
|
+
collection: userCollectionSlug
|
|
167
|
+
} };
|
|
168
|
+
} catch (error) {
|
|
169
|
+
if (error instanceof errors.JWTExpired) return getUnauthenticatedResult(Boolean(ctx.canSetHeaders));
|
|
170
|
+
return getUnauthenticatedResult(Boolean(ctx.canSetHeaders));
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
//#endregion
|
|
177
|
+
//#region src/providers/zitadel/zitadelUserHook.ts
|
|
178
|
+
function getZitadelAccessToken(authServiceUser) {
|
|
179
|
+
if (!authServiceUser) throw new Error("Missing authServiceUser");
|
|
180
|
+
return authServiceUser;
|
|
181
|
+
}
|
|
182
|
+
async function doesZitadelUserExist({ accessToken, authBaseUrl, email }) {
|
|
183
|
+
const searchResponse = await fetch(`${authBaseUrl}/v2/users`, {
|
|
184
|
+
body: JSON.stringify({ queries: [{ emailQuery: {
|
|
185
|
+
emailAddress: email,
|
|
186
|
+
method: "TEXT_QUERY_METHOD_EQUALS"
|
|
187
|
+
} }] }),
|
|
188
|
+
headers: {
|
|
189
|
+
"Authorization": `Bearer ${accessToken}`,
|
|
190
|
+
"Content-Type": "application/json"
|
|
191
|
+
},
|
|
192
|
+
method: "POST"
|
|
193
|
+
});
|
|
194
|
+
if (!searchResponse.ok) throw new Error(`Failed to search Zitadel users: ${await searchResponse.text()}`);
|
|
195
|
+
const searchBody = await searchResponse.json();
|
|
196
|
+
return Array.isArray(searchBody.result) && searchBody.result.length > 0;
|
|
197
|
+
}
|
|
198
|
+
function createZitadelUserHook({ env, shouldSkip }) {
|
|
199
|
+
return async ({ data, operation }) => {
|
|
200
|
+
if (operation !== "create") return;
|
|
201
|
+
if (shouldSkip?.(data)) return;
|
|
202
|
+
const email = data.email;
|
|
203
|
+
if (!email) throw new Error("Cannot create Zitadel user without email");
|
|
204
|
+
const accessToken = getZitadelAccessToken(env.authServiceUser);
|
|
205
|
+
if (await doesZitadelUserExist({
|
|
206
|
+
accessToken,
|
|
207
|
+
authBaseUrl: env.authBaseUrl,
|
|
208
|
+
email
|
|
209
|
+
})) return;
|
|
210
|
+
const displayName = [data.firstName, data.lastName].filter(Boolean).join(" ") || email;
|
|
211
|
+
const createResponse = await fetch(`${env.authBaseUrl}/v2/users/human`, {
|
|
212
|
+
body: JSON.stringify({
|
|
213
|
+
email: {
|
|
214
|
+
email,
|
|
215
|
+
sendCode: {}
|
|
216
|
+
},
|
|
217
|
+
organization: { orgId: env.authOrganizationId },
|
|
218
|
+
profile: {
|
|
219
|
+
displayName,
|
|
220
|
+
familyName: data.lastName || email,
|
|
221
|
+
givenName: data.firstName || email
|
|
222
|
+
}
|
|
223
|
+
}),
|
|
224
|
+
headers: {
|
|
225
|
+
"Authorization": `Bearer ${accessToken}`,
|
|
226
|
+
"Content-Type": "application/json"
|
|
227
|
+
},
|
|
228
|
+
method: "POST"
|
|
229
|
+
});
|
|
230
|
+
if (!createResponse.ok) throw new Error(`Failed to create Zitadel user: ${await createResponse.text()}`);
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
//#endregion
|
|
235
|
+
//#region src/providers/zitadel/zitadelProvider.ts
|
|
236
|
+
function createZitadelAuthProvider(env) {
|
|
237
|
+
return {
|
|
238
|
+
createStrategy: (params) => createZitadelAuthStrategy({
|
|
239
|
+
...params,
|
|
240
|
+
env
|
|
241
|
+
}),
|
|
242
|
+
createUserHook: (params) => createZitadelUserHook({
|
|
243
|
+
...params,
|
|
244
|
+
env
|
|
245
|
+
}),
|
|
246
|
+
getMiddlewareAuthConfig: () => ({
|
|
247
|
+
authClientId: env.authClientId,
|
|
248
|
+
authOrganizationId: env.authOrganizationId,
|
|
249
|
+
authBaseUrl: env.authBaseUrl,
|
|
250
|
+
provider: "zitadel"
|
|
251
|
+
}),
|
|
252
|
+
getPublicConfig: () => ({
|
|
253
|
+
authClientId: env.authClientId,
|
|
254
|
+
authOrganizationId: env.authOrganizationId,
|
|
255
|
+
authBaseUrl: env.authBaseUrl,
|
|
256
|
+
cmsBaseUrl: env.cmsBaseUrl,
|
|
257
|
+
provider: "zitadel"
|
|
258
|
+
})
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
//#endregion
|
|
263
|
+
export { AUTH_COOKIE_NAME, CODE_CHALLENGE_COOKIE_NAME, CODE_VERIFIER_COOKIE_NAME, DEFAULT_ACCESS_TOKEN_EXPIRATION, DEFAULT_LOCK_TIME, DEFAULT_MAX_LOGIN_ATTEMPTS, USER_NOT_AUTHENTICATED, createPayloadAuthPlugin, createPayloadCollectionAuth, createZitadelAuthProvider, createZitadelAuthStrategy, createZitadelUserHook, getAuthClientStrategy, getAuthData, getRemoveAuthCookieHeaders, getUnauthenticatedResult, removeAuthCookie, removePkceCookies, setAuthCookie, tokensSchema };
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from "next/server";
|
|
2
|
+
import { AuthStrategy, CollectionAfterChangeHook, CollectionConfig, Plugin, TypeWithID } from "payload";
|
|
3
|
+
|
|
4
|
+
//#region src/plugin/payloadCollectionAuth.d.ts
|
|
5
|
+
declare function createPayloadCollectionAuth({
|
|
6
|
+
lockTime,
|
|
7
|
+
maxLoginAttempts,
|
|
8
|
+
strategy,
|
|
9
|
+
tokenExpiration,
|
|
10
|
+
verify
|
|
11
|
+
}: CreatePayloadCollectionAuthParams & {
|
|
12
|
+
strategy: AuthStrategy;
|
|
13
|
+
}): NonNullable<CollectionConfig['auth']>;
|
|
14
|
+
//#endregion
|
|
15
|
+
//#region src/server/withPayloadTokenAuth.d.ts
|
|
16
|
+
interface AuthEnv$1 {
|
|
17
|
+
authClientId: string;
|
|
18
|
+
authOrganizationId: string;
|
|
19
|
+
authBaseUrl: string;
|
|
20
|
+
provider: AuthProviderType;
|
|
21
|
+
}
|
|
22
|
+
interface MiddlewareState {
|
|
23
|
+
request: NextRequest;
|
|
24
|
+
response: NextResponse;
|
|
25
|
+
}
|
|
26
|
+
interface WithPayloadTokenAuthParams {
|
|
27
|
+
auth: AuthEnv$1;
|
|
28
|
+
loginPath?: string;
|
|
29
|
+
tokenRefreshBufferMs?: number;
|
|
30
|
+
}
|
|
31
|
+
declare function withPayloadTokenAuth(middlewareState: MiddlewareState, {
|
|
32
|
+
auth,
|
|
33
|
+
loginPath,
|
|
34
|
+
tokenRefreshBufferMs
|
|
35
|
+
}: WithPayloadTokenAuthParams): Promise<MiddlewareState>;
|
|
36
|
+
//#endregion
|
|
37
|
+
//#region src/shared/payloadAuth.types.d.ts
|
|
38
|
+
interface BaseUserRecord {
|
|
39
|
+
email?: string | null;
|
|
40
|
+
firstName?: string | null;
|
|
41
|
+
lastName?: string | null;
|
|
42
|
+
role?: string | null;
|
|
43
|
+
username?: string;
|
|
44
|
+
}
|
|
45
|
+
interface BaseUserRecordWithId extends BaseUserRecord {
|
|
46
|
+
id: number | string;
|
|
47
|
+
}
|
|
48
|
+
interface AuthEnv {
|
|
49
|
+
authClientId: string;
|
|
50
|
+
authOrganizationId: string;
|
|
51
|
+
authBaseUrl: string;
|
|
52
|
+
authIssuer: string;
|
|
53
|
+
authJwksEndpoint: string;
|
|
54
|
+
authServiceUser: string;
|
|
55
|
+
cmsBaseUrl: string;
|
|
56
|
+
}
|
|
57
|
+
type AuthProviderType = 'zitadel';
|
|
58
|
+
interface CreateFirstUserConfig {
|
|
59
|
+
createTenantData?: () => Record<string, unknown>;
|
|
60
|
+
tenantCollectionSlug: string;
|
|
61
|
+
userData: (params: {
|
|
62
|
+
tenantId: number | string;
|
|
63
|
+
isFirstUser: boolean;
|
|
64
|
+
email: string;
|
|
65
|
+
}) => Record<string, unknown>;
|
|
66
|
+
}
|
|
67
|
+
interface CreateZitadelAuthStrategyParams<TUser extends BaseUserRecord> {
|
|
68
|
+
isUserAllowed: (user: TUser) => boolean;
|
|
69
|
+
createFirstUser?: CreateFirstUserConfig;
|
|
70
|
+
env: AuthEnv;
|
|
71
|
+
userCollectionSlug: string;
|
|
72
|
+
}
|
|
73
|
+
interface CreateZitadelUserHookParams<TUser extends BaseUserRecord & TypeWithID> {
|
|
74
|
+
env: AuthEnv;
|
|
75
|
+
shouldSkip?: (user: Partial<TUser>) => boolean;
|
|
76
|
+
}
|
|
77
|
+
interface CreatePayloadCollectionAuthParams {
|
|
78
|
+
lockTime?: number;
|
|
79
|
+
maxLoginAttempts?: number;
|
|
80
|
+
strategy: AuthStrategy;
|
|
81
|
+
tokenExpiration?: number;
|
|
82
|
+
verify?: boolean;
|
|
83
|
+
}
|
|
84
|
+
interface PublicAuthConfig {
|
|
85
|
+
authClientId: string;
|
|
86
|
+
authOrganizationId: string;
|
|
87
|
+
authBaseUrl: string;
|
|
88
|
+
cmsBaseUrl: string;
|
|
89
|
+
provider: AuthProviderType;
|
|
90
|
+
}
|
|
91
|
+
interface CreateAuthStrategyParams<TUser extends BaseUserRecord> {
|
|
92
|
+
isUserAllowed: (user: TUser) => boolean;
|
|
93
|
+
createFirstUser?: CreateFirstUserConfig;
|
|
94
|
+
userCollectionSlug: string;
|
|
95
|
+
}
|
|
96
|
+
interface CreateAuthUserHookParams<TUser extends BaseUserRecord & TypeWithID> {
|
|
97
|
+
shouldSkip?: (user: Partial<TUser>) => boolean;
|
|
98
|
+
}
|
|
99
|
+
interface PayloadAuthProvider<TUser extends BaseUserRecord & TypeWithID> {
|
|
100
|
+
createStrategy: (params: CreateAuthStrategyParams<TUser>) => AuthStrategy;
|
|
101
|
+
createUserHook: (params: CreateAuthUserHookParams<TUser>) => CollectionAfterChangeHook<TUser>;
|
|
102
|
+
getMiddlewareAuthConfig: () => Pick<PublicAuthConfig, 'authBaseUrl' | 'authClientId' | 'authOrganizationId' | 'provider'>;
|
|
103
|
+
getPublicConfig: () => PublicAuthConfig;
|
|
104
|
+
}
|
|
105
|
+
interface CreatePayloadAuthPluginParams<TUser extends BaseUserRecord & TypeWithID> {
|
|
106
|
+
isUserAllowed: (user: TUser) => boolean;
|
|
107
|
+
authConfig?: Omit<CreatePayloadCollectionAuthParams, 'strategy'>;
|
|
108
|
+
createFirstUser?: CreateFirstUserConfig;
|
|
109
|
+
provider: PayloadAuthProvider<TUser>;
|
|
110
|
+
shouldSkipUserSync?: (user: Partial<TUser>) => boolean;
|
|
111
|
+
tenantCollectionSlug: string;
|
|
112
|
+
tokenRefreshBufferMs?: number;
|
|
113
|
+
userCollectionSlug: string;
|
|
114
|
+
}
|
|
115
|
+
interface PayloadAuthPluginResult {
|
|
116
|
+
auth: PublicAuthConfig;
|
|
117
|
+
createCollectionAuth: (overrides?: Omit<CreatePayloadCollectionAuthParams, 'strategy'>) => ReturnType<typeof createPayloadCollectionAuth>;
|
|
118
|
+
getCallbackViewProps: (overrides?: Partial<PublicAuthConfig>) => {
|
|
119
|
+
clientId: string;
|
|
120
|
+
organizationId: string;
|
|
121
|
+
authBaseUrl: string;
|
|
122
|
+
cmsBaseUrl: string;
|
|
123
|
+
provider: AuthProviderType;
|
|
124
|
+
};
|
|
125
|
+
getLoginButtonProps: (overrides?: Partial<PublicAuthConfig>) => {
|
|
126
|
+
clientId: string;
|
|
127
|
+
organizationId: string;
|
|
128
|
+
authBaseUrl: string;
|
|
129
|
+
cmsBaseUrl: string;
|
|
130
|
+
provider: AuthProviderType;
|
|
131
|
+
};
|
|
132
|
+
middleware: (middlewareState: Parameters<typeof withPayloadTokenAuth>[0]) => Promise<Awaited<ReturnType<typeof withPayloadTokenAuth>>>;
|
|
133
|
+
plugin: () => Plugin;
|
|
134
|
+
strategy: AuthStrategy;
|
|
135
|
+
userHook: CollectionAfterChangeHook<any>;
|
|
136
|
+
}
|
|
137
|
+
//#endregion
|
|
138
|
+
export { CreateAuthStrategyParams as a, CreatePayloadAuthPluginParams as c, CreateZitadelUserHookParams as d, PayloadAuthPluginResult as f, createPayloadCollectionAuth as g, withPayloadTokenAuth as h, BaseUserRecordWithId as i, CreatePayloadCollectionAuthParams as l, PublicAuthConfig as m, AuthProviderType as n, CreateAuthUserHookParams as o, PayloadAuthProvider as p, BaseUserRecord as r, CreateFirstUserConfig as s, AuthEnv as t, CreateZitadelAuthStrategyParams as u };
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { h as withPayloadTokenAuth, n as AuthProviderType } from "./payloadAuth.types-pFZl0rQd.mjs";
|
|
2
|
+
import { NextRequest, NextResponse } from "next/server";
|
|
3
|
+
|
|
4
|
+
//#region src/server/refreshToken.d.ts
|
|
5
|
+
interface RefreshTokenParams {
|
|
6
|
+
clientId: string;
|
|
7
|
+
organizationId: string;
|
|
8
|
+
authBaseUrl: string;
|
|
9
|
+
provider: AuthProviderType;
|
|
10
|
+
req: NextRequest;
|
|
11
|
+
res: NextResponse;
|
|
12
|
+
}
|
|
13
|
+
declare function refreshToken({
|
|
14
|
+
clientId,
|
|
15
|
+
organizationId,
|
|
16
|
+
authBaseUrl,
|
|
17
|
+
provider,
|
|
18
|
+
req,
|
|
19
|
+
res
|
|
20
|
+
}: RefreshTokenParams): Promise<{
|
|
21
|
+
expiresAt: number;
|
|
22
|
+
accessToken: string;
|
|
23
|
+
refreshToken: string;
|
|
24
|
+
}>;
|
|
25
|
+
//#endregion
|
|
26
|
+
export { refreshToken, withPayloadTokenAuth };
|
package/dist/server.mjs
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { a as getRemoveAuthCookieHeaders, c as setAuthCookie, i as getAuthData, u as getAuthClientStrategy } from "./authData-BieBPsYC.mjs";
|
|
2
|
+
import { NextResponse } from "next/server";
|
|
3
|
+
|
|
4
|
+
//#region src/server/refreshToken.ts
|
|
5
|
+
async function refreshToken({ clientId, organizationId, authBaseUrl, provider, req, res }) {
|
|
6
|
+
const authData = await getAuthData({
|
|
7
|
+
req,
|
|
8
|
+
res
|
|
9
|
+
});
|
|
10
|
+
if (authData == null) throw new Error("Auth data not found");
|
|
11
|
+
return setAuthCookie(await getAuthClientStrategy(provider).refreshToken({
|
|
12
|
+
clientId,
|
|
13
|
+
organizationId,
|
|
14
|
+
authBaseUrl,
|
|
15
|
+
refreshToken: authData.refreshToken
|
|
16
|
+
}), {
|
|
17
|
+
req,
|
|
18
|
+
res
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
//#endregion
|
|
23
|
+
//#region src/server/withPayloadTokenAuth.ts
|
|
24
|
+
async function withPayloadTokenAuth(middlewareState, { auth, loginPath = "/login", tokenRefreshBufferMs = 3e4 }) {
|
|
25
|
+
const { request, response } = middlewareState;
|
|
26
|
+
if (request.nextUrl.pathname === loginPath) return middlewareState;
|
|
27
|
+
const authData = await getAuthData({
|
|
28
|
+
req: request,
|
|
29
|
+
res: response
|
|
30
|
+
});
|
|
31
|
+
if (authData == null) return middlewareState;
|
|
32
|
+
let accessToken = authData.accessToken;
|
|
33
|
+
if (authData.expiresAt <= Date.now() + tokenRefreshBufferMs) try {
|
|
34
|
+
accessToken = (await refreshToken({
|
|
35
|
+
clientId: auth.authClientId,
|
|
36
|
+
organizationId: auth.authOrganizationId,
|
|
37
|
+
authBaseUrl: auth.authBaseUrl,
|
|
38
|
+
provider: auth.provider,
|
|
39
|
+
req: request,
|
|
40
|
+
res: response
|
|
41
|
+
})).accessToken;
|
|
42
|
+
} catch {
|
|
43
|
+
const unauthenticatedResponse = NextResponse.next({ headers: response.headers });
|
|
44
|
+
for (const [key, value] of getRemoveAuthCookieHeaders().entries()) unauthenticatedResponse.headers.append(key, value);
|
|
45
|
+
return {
|
|
46
|
+
request,
|
|
47
|
+
response: unauthenticatedResponse
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
const requestHeaders = new Headers(request.headers);
|
|
51
|
+
requestHeaders.set("Authorization", `Bearer ${accessToken}`);
|
|
52
|
+
return {
|
|
53
|
+
request,
|
|
54
|
+
response: NextResponse.next({
|
|
55
|
+
headers: response.headers,
|
|
56
|
+
request: { headers: requestHeaders }
|
|
57
|
+
})
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
//#endregion
|
|
62
|
+
export { refreshToken as n, withPayloadTokenAuth as t };
|
package/package.json
CHANGED
|
@@ -1,9 +1,71 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wisemen/payload-core-auth",
|
|
3
|
-
"
|
|
3
|
+
"type": "module",
|
|
4
|
+
"version": "0.0.2",
|
|
5
|
+
"private": false,
|
|
6
|
+
"imports": {
|
|
7
|
+
"#*": "./src/*"
|
|
8
|
+
},
|
|
4
9
|
"repository": {
|
|
5
10
|
"type": "git",
|
|
6
11
|
"url": "https://github.com/wisemen-digital/wisemen-core",
|
|
7
12
|
"directory": "packages/payload/auth"
|
|
13
|
+
},
|
|
14
|
+
"exports": {
|
|
15
|
+
".": {
|
|
16
|
+
"types": "./dist/index.d.mts",
|
|
17
|
+
"import": "./dist/index.mjs"
|
|
18
|
+
},
|
|
19
|
+
"./client": {
|
|
20
|
+
"types": "./dist/client.d.mts",
|
|
21
|
+
"import": "./dist/client.mjs"
|
|
22
|
+
},
|
|
23
|
+
"./server": {
|
|
24
|
+
"types": "./dist/server.d.mts",
|
|
25
|
+
"import": "./dist/server.mjs"
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
"main": "./dist/index.mjs",
|
|
29
|
+
"module": "./dist/index.mjs",
|
|
30
|
+
"types": "./dist/index.d.mts",
|
|
31
|
+
"typings": "./dist/index.d.mts",
|
|
32
|
+
"sideEffects": false,
|
|
33
|
+
"files": [
|
|
34
|
+
"dist"
|
|
35
|
+
],
|
|
36
|
+
"peerDependencies": {
|
|
37
|
+
"@payloadcms/ui": ">=3.85.0",
|
|
38
|
+
"next": ">=16.2.6",
|
|
39
|
+
"payload": ">=3.85.0",
|
|
40
|
+
"react": ">=19.2.7",
|
|
41
|
+
"@wisemen/payload-core-utils": "^0.0.2"
|
|
42
|
+
},
|
|
43
|
+
"dependencies": {
|
|
44
|
+
"cookies-next": "6.1.1",
|
|
45
|
+
"jose": "6.2.3",
|
|
46
|
+
"pkce-challenge": "6.0.0",
|
|
47
|
+
"zod": "4.3.5"
|
|
48
|
+
},
|
|
49
|
+
"devDependencies": {
|
|
50
|
+
"@payloadcms/ui": "3.85.2",
|
|
51
|
+
"@types/node": "24.8.1",
|
|
52
|
+
"@types/react": "19.2.17",
|
|
53
|
+
"@wisemen/eslint-config-vue": "2.1.3",
|
|
54
|
+
"eslint": "9.39.4",
|
|
55
|
+
"next": "16.2.6",
|
|
56
|
+
"payload": "3.85.2",
|
|
57
|
+
"react": "19.2.7",
|
|
58
|
+
"rimraf": "6.1.3",
|
|
59
|
+
"tsdown": "0.18.4",
|
|
60
|
+
"typescript": "5.9.3",
|
|
61
|
+
"@wisemen/payload-core-utils": "^0.0.2"
|
|
62
|
+
},
|
|
63
|
+
"scripts": {
|
|
64
|
+
"build": "tsdown",
|
|
65
|
+
"dev": "tsdown --watch",
|
|
66
|
+
"cleanup": "rimraf dist/ node_modules/ .turbo/",
|
|
67
|
+
"lint": "eslint",
|
|
68
|
+
"type-check": "tsc --noEmit",
|
|
69
|
+
"lint:fix": "eslint --fix"
|
|
8
70
|
}
|
|
9
|
-
}
|
|
71
|
+
}
|