@tetherto/dev-websites-core 0.3.2 → 0.3.4
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/README.md +74 -0
- package/dist/cms/google-oauth.d.mts +20 -0
- package/dist/cms/google-oauth.d.mts.map +1 -0
- package/dist/cms/google-oauth.mjs +130 -0
- package/dist/cms/google-oauth.mjs.map +1 -0
- package/dist/cms/index.d.mts +2 -2
- package/dist/cms/index.mjs +2 -2
- package/dist/cms.mjs +436 -7
- package/dist/cms.mjs.map +1 -1
- package/dist/index.d.mts +24 -3
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +4 -3
- package/dist/index5.d.mts +2 -2
- package/dist/seo/index.mjs +2 -1
- package/dist/seo.mjs +2 -13
- package/dist/seo.mjs.map +1 -1
- package/dist/site-url.mjs +16 -0
- package/dist/site-url.mjs.map +1 -0
- package/dist/ui/components/Badge/Badge.d.mts +1 -1
- package/dist/ui/components/GoogleOAuthButton/GoogleOAuthButton.d.mts +5 -0
- package/dist/ui/components/GoogleOAuthButton/GoogleOAuthButton.d.mts.map +1 -0
- package/dist/ui/components/GoogleOAuthButton/GoogleOAuthButton.mjs +72 -0
- package/dist/ui/components/GoogleOAuthButton/GoogleOAuthButton.mjs.map +1 -0
- package/dist/ui/components/GoogleOAuthButton/googleOAuthStartPath.d.mts +6 -0
- package/dist/ui/components/GoogleOAuthButton/googleOAuthStartPath.d.mts.map +1 -0
- package/dist/ui/components/GoogleOAuthButton/googleOAuthStartPath.mjs +7 -0
- package/dist/ui/components/GoogleOAuthButton/googleOAuthStartPath.mjs.map +1 -0
- package/dist/ui/components/GoogleOAuthButton/index.d.mts +2 -0
- package/dist/ui/components/GoogleOAuthButton/index.mjs +2 -0
- package/dist/ui/index.d.mts +3 -1
- package/dist/ui/index.mjs +1 -0
- package/package.json +18 -2
- package/src/ui/components/GoogleOAuthButton/GoogleOAuthButton.tsx +69 -0
- package/src/ui/components/GoogleOAuthButton/googleOAuthStartPath.ts +2 -0
- package/src/ui/components/GoogleOAuthButton/index.ts +1 -0
package/README.md
CHANGED
|
@@ -5,11 +5,13 @@ Shared CMS, SEO, i18n, utils, UI, and analytics primitives for WDK / QVAC / PEAR
|
|
|
5
5
|
| Import | Contents |
|
|
6
6
|
| --- | --- |
|
|
7
7
|
| `@tetherto/dev-websites-core/cms` | Payload `buildCmsConfig`, stock collections, access, Lexical, media I/O |
|
|
8
|
+
| `@tetherto/dev-websites-core/cms/google-oauth` | Google admin login handlers (`google-auth-library` peer) |
|
|
8
9
|
| `@tetherto/dev-websites-core/seo` | `createMetadataFactory`, `getSiteUrl`, `getOgBackgroundDataUrl` |
|
|
9
10
|
| `@tetherto/dev-websites-core/i18n` | `Languages` / `LanguageLabel` |
|
|
10
11
|
| `@tetherto/dev-websites-core/utils` | `createLogger`, `toPlainValue`, `stripShikiPreBackground` |
|
|
11
12
|
| `@tetherto/dev-websites-core/analytics` | `GtmLoader`, `createTrackedLink` (optional `@next/third-parties` peer) |
|
|
12
13
|
| `@tetherto/dev-websites-core/ui` | UI primitives (`Button`, `Dialog`, `Pagination`, `Tooltip`, …) + `cn` |
|
|
14
|
+
| `@tetherto/dev-websites-core/ui/google-oauth` | Payload `GoogleOAuthButton` (Tailwind classes; source `dist` from Payload CSS) |
|
|
13
15
|
| `@tetherto/dev-websites-core/ui/a11y` | `focusRingClassName`, `VisuallyHidden` |
|
|
14
16
|
| `@tetherto/dev-websites-core/ui/carousel` | `Carousel` (+ optional `embla-carousel-react` peer) |
|
|
15
17
|
| `@tetherto/dev-websites-core/ui/theme.css` | One-shot: tokens + Tailwind v4 `@theme` preset |
|
|
@@ -51,6 +53,71 @@ rm -rf node_modules/@tetherto/dev-websites-core && npm install @tetherto/dev-web
|
|
|
51
53
|
Do **not** import `@tetherto/dev-websites-core/cms` from Client Components — it includes Node-only
|
|
52
54
|
modules (`fs`, Payload, S3). Prefer domain subpaths over the root barrel.
|
|
53
55
|
|
|
56
|
+
### Google admin login
|
|
57
|
+
|
|
58
|
+
Stock `users` includes `googleId` / `name` / `picture`. Sites add Next.js routes and
|
|
59
|
+
Payload `afterLogin` (optional `google-auth-library` peer):
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
// src/payload.config.ts
|
|
63
|
+
import { GOOGLE_OAUTH_AFTER_LOGIN } from '@tetherto/dev-websites-core/cms/google-oauth'
|
|
64
|
+
|
|
65
|
+
admin: {
|
|
66
|
+
components: { afterLogin: [...GOOGLE_OAUTH_AFTER_LOGIN] },
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
```ts
|
|
71
|
+
// src/app/api/auth/google/route.ts
|
|
72
|
+
import { handleGoogleOAuthStart } from '@tetherto/dev-websites-core/cms/google-oauth'
|
|
73
|
+
|
|
74
|
+
export function GET() {
|
|
75
|
+
return handleGoogleOAuthStart({
|
|
76
|
+
clientId: process.env.GOOGLE_OAUTH_CLIENT_ID ?? '',
|
|
77
|
+
clientSecret: process.env.GOOGLE_OAUTH_CLIENT_SECRET ?? '',
|
|
78
|
+
})
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
```ts
|
|
83
|
+
// src/app/api/auth/google/callback/route.ts
|
|
84
|
+
import { handleGoogleOAuthCallback } from '@tetherto/dev-websites-core/cms/google-oauth'
|
|
85
|
+
import config from '@payload-config'
|
|
86
|
+
|
|
87
|
+
import type { NextRequest } from 'next/server'
|
|
88
|
+
|
|
89
|
+
export function GET(request: NextRequest) {
|
|
90
|
+
return handleGoogleOAuthCallback(request, {
|
|
91
|
+
config,
|
|
92
|
+
clientId: process.env.GOOGLE_OAUTH_CLIENT_ID ?? '',
|
|
93
|
+
clientSecret: process.env.GOOGLE_OAUTH_CLIENT_SECRET ?? '',
|
|
94
|
+
})
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
```css
|
|
99
|
+
/* src/app/(payload)/custom.css — utilities only; do not import Tailwind preflight */
|
|
100
|
+
@import 'tailwindcss/theme.css' layer(theme) source(none);
|
|
101
|
+
@import 'tailwindcss/utilities.css' layer(utilities) source(none);
|
|
102
|
+
@source '../../../node_modules/@tetherto/dev-websites-core/src/ui/components/GoogleOAuthButton';
|
|
103
|
+
|
|
104
|
+
.login__form > .form-submit .btn {
|
|
105
|
+
margin-bottom: 0;
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Apps using Next.js should also transpile the package so the Payload button shares the app React instance:
|
|
110
|
+
|
|
111
|
+
```ts
|
|
112
|
+
// next.config.ts
|
|
113
|
+
transpilePackages: ['@tetherto/dev-websites-core']
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
The app passes `clientId` and `clientSecret` into both handlers (usually from its own
|
|
117
|
+
`GOOGLE_OAUTH_CLIENT_ID` and `GOOGLE_OAUTH_CLIENT_SECRET`). Redirect URI:
|
|
118
|
+
`{NEXT_PUBLIC_SITE_URL}/api/auth/google/callback`. Existing `users` emails are
|
|
119
|
+
required — Google login does not create accounts.
|
|
120
|
+
|
|
54
121
|
### Analytics
|
|
55
122
|
|
|
56
123
|
```ts
|
|
@@ -133,6 +200,8 @@ Registered by `buildCmsConfig` in this order. Disable or extend any of them via
|
|
|
133
200
|
| `legal-pages` | collection | Legal/policy content pages (same shape as `pages`). |
|
|
134
201
|
| `changelog-tags` | collection | Editor-managed tags shown on changelog entries. |
|
|
135
202
|
| `changelog` | collection | Changelog entries — **shared fields only** (see below). |
|
|
203
|
+
| `event-tags` | collection | Editor-managed event categories (Meetup, Workshop, Hackathon, …). Related from `events`. |
|
|
204
|
+
| `events` | collection | Events — drafts + autosave, start/end, timezone, location, tag, external URL, optional cover/body. |
|
|
136
205
|
| `site-settings` | global | Site name, default description/OG image, organization + social. |
|
|
137
206
|
|
|
138
207
|
Changelog stock ships shared fields only; site-specific body / GitHub /
|
|
@@ -140,6 +209,11 @@ Changelog stock ships shared fields only; site-specific body / GitHub /
|
|
|
140
209
|
`overrides.hooks` (site-owned, e.g. `src/lib/cache`) — hook arrays are **appended**
|
|
141
210
|
to stock hooks, not replaced.
|
|
142
211
|
|
|
212
|
+
Draft collections enable Payload `schedulePublish`. `buildCmsConfig` starts
|
|
213
|
+
`jobs.autoRun` every minute so scheduled publishes run in a long-lived Node
|
|
214
|
+
process (`next start` / Docker). Override or disable via `payload.jobs`. This
|
|
215
|
+
is not for serverless hosts.
|
|
216
|
+
|
|
143
217
|
### Peer dependencies
|
|
144
218
|
|
|
145
219
|
CMS / Next (required for `/cms` consumers):
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Config, SanitizedConfig } from "payload";
|
|
2
|
+
import { NextRequest, NextResponse } from "next/server.js";
|
|
3
|
+
//#region src/cms/auth/google-oauth.d.ts
|
|
4
|
+
/** Payload `admin.components.afterLogin` path for the shared Google button. */
|
|
5
|
+
declare const GOOGLE_OAUTH_AFTER_LOGIN: readonly ["@tetherto/dev-websites-core/ui/google-oauth#GoogleOAuthButton"];
|
|
6
|
+
type GoogleOAuthClientConfig = {
|
|
7
|
+
clientId: string;
|
|
8
|
+
clientSecret: string;
|
|
9
|
+
};
|
|
10
|
+
type HandleGoogleOAuthCallbackOptions = GoogleOAuthClientConfig & {
|
|
11
|
+
/** Site Payload config default export (`import config from '@payload-config'`). */
|
|
12
|
+
config: Config | SanitizedConfig | Promise<Config | SanitizedConfig>;
|
|
13
|
+
};
|
|
14
|
+
/** GET `/api/auth/google` — start the Google OAuth redirect. */
|
|
15
|
+
declare function handleGoogleOAuthStart(clientConfig: GoogleOAuthClientConfig): Promise<NextResponse<unknown>>;
|
|
16
|
+
/** GET `/api/auth/google/callback` — exchange the code and set the Payload session cookie. */
|
|
17
|
+
declare function handleGoogleOAuthCallback(request: NextRequest, { config, ...clientConfig }: HandleGoogleOAuthCallbackOptions): Promise<NextResponse<unknown>>;
|
|
18
|
+
//#endregion
|
|
19
|
+
export { GOOGLE_OAUTH_AFTER_LOGIN, GoogleOAuthClientConfig, HandleGoogleOAuthCallbackOptions, handleGoogleOAuthCallback, handleGoogleOAuthStart };
|
|
20
|
+
//# sourceMappingURL=google-oauth.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"google-oauth.d.mts","names":[],"sources":["../../src/cms/auth/google-oauth.ts"],"mappings":";;;;cAiBa;KAMD;EACV;EACA;;KAGU,mCAAmC;;EAE7C,QAAQ,SAAS,kBAAkB,QAAQ,SAAS;;;iBAqBhC,uBAAuB,cAAc,0BAAuB,QAAA;;iBAoB5D,0BACpB,SAAS,eACP,WAAW,gBAAgB,mCAAgC,QAAA"}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { t as createLogger } from "../logger.mjs";
|
|
2
|
+
import { t as getSiteUrl } from "../site-url.mjs";
|
|
3
|
+
import { getFieldsToSign, getPayload, jwtSign } from "payload";
|
|
4
|
+
import { OAuth2Client } from "google-auth-library";
|
|
5
|
+
import { NextResponse } from "next/server.js";
|
|
6
|
+
import { addSessionToUser, generatePayloadCookie } from "payload/shared";
|
|
7
|
+
//#region src/cms/auth/google-oauth.ts
|
|
8
|
+
const log = createLogger("googleOAuth");
|
|
9
|
+
const GOOGLE_OAUTH_CALLBACK_PATH = "/api/auth/google/callback";
|
|
10
|
+
const GOOGLE_OAUTH_LOGIN_PATH = "/admin/login";
|
|
11
|
+
const GOOGLE_OAUTH_ADMIN_PATH = "/admin";
|
|
12
|
+
/** Payload `admin.components.afterLogin` path for the shared Google button. */
|
|
13
|
+
const GOOGLE_OAUTH_AFTER_LOGIN = ["@tetherto/dev-websites-core/ui/google-oauth#GoogleOAuthButton"];
|
|
14
|
+
const GOOGLE_OAUTH_SCOPES = [
|
|
15
|
+
"openid",
|
|
16
|
+
"email",
|
|
17
|
+
"profile"
|
|
18
|
+
];
|
|
19
|
+
function getGoogleOAuthClient(redirectUri, { clientId, clientSecret }) {
|
|
20
|
+
if (!clientId || !clientSecret) throw new Error("Google OAuth credentials not configured");
|
|
21
|
+
return new OAuth2Client(clientId, clientSecret, redirectUri);
|
|
22
|
+
}
|
|
23
|
+
function loginErrorRedirect(siteUrl, error) {
|
|
24
|
+
return NextResponse.redirect(`${siteUrl}${GOOGLE_OAUTH_LOGIN_PATH}?error=${encodeURIComponent(error)}`);
|
|
25
|
+
}
|
|
26
|
+
/** GET `/api/auth/google` — start the Google OAuth redirect. */
|
|
27
|
+
async function handleGoogleOAuthStart(clientConfig) {
|
|
28
|
+
try {
|
|
29
|
+
const authorizeUrl = getGoogleOAuthClient(`${getSiteUrl()}${GOOGLE_OAUTH_CALLBACK_PATH}`, clientConfig).generateAuthUrl({
|
|
30
|
+
access_type: "offline",
|
|
31
|
+
scope: [...GOOGLE_OAUTH_SCOPES],
|
|
32
|
+
prompt: "select_account"
|
|
33
|
+
});
|
|
34
|
+
return NextResponse.redirect(authorizeUrl);
|
|
35
|
+
} catch (error) {
|
|
36
|
+
log.error(error instanceof Error ? error.message : "OAuth configuration error");
|
|
37
|
+
return NextResponse.json({ error: "OAuth configuration error" }, { status: 500 });
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
/** GET `/api/auth/google/callback` — exchange the code and set the Payload session cookie. */
|
|
41
|
+
async function handleGoogleOAuthCallback(request, { config, ...clientConfig }) {
|
|
42
|
+
try {
|
|
43
|
+
const { searchParams } = new URL(request.url);
|
|
44
|
+
const siteUrl = getSiteUrl();
|
|
45
|
+
const code = searchParams.get("code");
|
|
46
|
+
const error = searchParams.get("error");
|
|
47
|
+
if (error) return loginErrorRedirect(siteUrl, error);
|
|
48
|
+
if (!code) return loginErrorRedirect(siteUrl, "no_code");
|
|
49
|
+
const client = getGoogleOAuthClient(`${siteUrl}${GOOGLE_OAUTH_CALLBACK_PATH}`, clientConfig);
|
|
50
|
+
const { tokens } = await client.getToken(code);
|
|
51
|
+
client.setCredentials(tokens);
|
|
52
|
+
if (!tokens.id_token) return loginErrorRedirect(siteUrl, "no_token");
|
|
53
|
+
const googlePayload = (await client.verifyIdToken({
|
|
54
|
+
idToken: tokens.id_token,
|
|
55
|
+
audience: clientConfig.clientId
|
|
56
|
+
})).getPayload();
|
|
57
|
+
if (!googlePayload?.email) return loginErrorRedirect(siteUrl, "no_email");
|
|
58
|
+
const { email, sub: googleId, name, picture } = googlePayload;
|
|
59
|
+
const payload = await getPayload({ config: await config });
|
|
60
|
+
const users = await payload.find({
|
|
61
|
+
collection: "users",
|
|
62
|
+
where: { or: [{ email: { equals: email } }, { googleId: { equals: googleId } }] },
|
|
63
|
+
limit: 1
|
|
64
|
+
});
|
|
65
|
+
let user = users.docs[0];
|
|
66
|
+
if (users.docs.length > 0) {
|
|
67
|
+
user = users.docs[0];
|
|
68
|
+
const googleUser = user;
|
|
69
|
+
const updateData = {};
|
|
70
|
+
if (!googleUser.googleId) {
|
|
71
|
+
updateData.googleId = googleId;
|
|
72
|
+
updateData.name = name || googleUser.name;
|
|
73
|
+
updateData.picture = picture || googleUser.picture;
|
|
74
|
+
}
|
|
75
|
+
if (Object.keys(updateData).length > 0) user = await payload.update({
|
|
76
|
+
collection: "users",
|
|
77
|
+
id: user.id,
|
|
78
|
+
data: updateData
|
|
79
|
+
});
|
|
80
|
+
} else return loginErrorRedirect(siteUrl, "user_not_found");
|
|
81
|
+
const collectionConfig = payload.collections["users"].config;
|
|
82
|
+
const sessionUser = user;
|
|
83
|
+
const { sid } = await addSessionToUser({
|
|
84
|
+
collectionConfig,
|
|
85
|
+
payload,
|
|
86
|
+
req: {
|
|
87
|
+
payload,
|
|
88
|
+
user: sessionUser
|
|
89
|
+
},
|
|
90
|
+
user: sessionUser
|
|
91
|
+
});
|
|
92
|
+
const fieldsToSign = getFieldsToSign({
|
|
93
|
+
collectionConfig,
|
|
94
|
+
email: sessionUser.email,
|
|
95
|
+
sid,
|
|
96
|
+
user: sessionUser
|
|
97
|
+
});
|
|
98
|
+
const tokenExpiration = collectionConfig.auth.tokenExpiration || 7200;
|
|
99
|
+
const secret = payload.secret;
|
|
100
|
+
const { token } = await jwtSign({
|
|
101
|
+
fieldsToSign,
|
|
102
|
+
secret,
|
|
103
|
+
tokenExpiration
|
|
104
|
+
});
|
|
105
|
+
const cookie = generatePayloadCookie({
|
|
106
|
+
collectionAuthConfig: collectionConfig.auth,
|
|
107
|
+
cookiePrefix: payload.config.cookiePrefix || "payload",
|
|
108
|
+
token,
|
|
109
|
+
returnCookieAsObject: true
|
|
110
|
+
});
|
|
111
|
+
const response = NextResponse.redirect(`${siteUrl}${GOOGLE_OAUTH_ADMIN_PATH}`);
|
|
112
|
+
response.cookies.set(cookie.name, cookie.value, {
|
|
113
|
+
httpOnly: cookie.httpOnly,
|
|
114
|
+
sameSite: cookie.sameSite?.toLowerCase(),
|
|
115
|
+
secure: cookie.secure,
|
|
116
|
+
path: cookie.path,
|
|
117
|
+
expires: cookie.expires ? new Date(cookie.expires) : void 0,
|
|
118
|
+
maxAge: cookie.maxAge
|
|
119
|
+
});
|
|
120
|
+
return response;
|
|
121
|
+
} catch (error) {
|
|
122
|
+
const errorMessage = error instanceof Error ? error.message : "Authentication failed";
|
|
123
|
+
log.error(errorMessage);
|
|
124
|
+
return loginErrorRedirect(getSiteUrl(), errorMessage);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
//#endregion
|
|
128
|
+
export { GOOGLE_OAUTH_AFTER_LOGIN, handleGoogleOAuthCallback, handleGoogleOAuthStart };
|
|
129
|
+
|
|
130
|
+
//# sourceMappingURL=google-oauth.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"google-oauth.mjs","names":[],"sources":["../../src/cms/auth/google-oauth.ts"],"sourcesContent":["import { OAuth2Client } from 'google-auth-library'\nimport { NextRequest, NextResponse } from 'next/server'\nimport { getFieldsToSign, getPayload, jwtSign } from 'payload'\nimport { addSessionToUser, generatePayloadCookie } from 'payload/shared'\n\nimport { getSiteUrl } from '../../seo/site-url.js'\nimport { createLogger } from '../../utils/logger/logger.js'\n\nimport type { Config, SanitizedConfig } from 'payload'\n\nconst log = createLogger('googleOAuth')\n\nconst GOOGLE_OAUTH_CALLBACK_PATH = '/api/auth/google/callback'\nconst GOOGLE_OAUTH_LOGIN_PATH = '/admin/login'\nconst GOOGLE_OAUTH_ADMIN_PATH = '/admin'\n\n/** Payload `admin.components.afterLogin` path for the shared Google button. */\nexport const GOOGLE_OAUTH_AFTER_LOGIN = [\n '@tetherto/dev-websites-core/ui/google-oauth#GoogleOAuthButton',\n] as const\n\nconst GOOGLE_OAUTH_SCOPES = ['openid', 'email', 'profile'] as const\n\nexport type GoogleOAuthClientConfig = {\n clientId: string\n clientSecret: string\n}\n\nexport type HandleGoogleOAuthCallbackOptions = GoogleOAuthClientConfig & {\n /** Site Payload config default export (`import config from '@payload-config'`). */\n config: Config | SanitizedConfig | Promise<Config | SanitizedConfig>\n}\n\nfunction getGoogleOAuthClient(\n redirectUri: string,\n { clientId, clientSecret }: GoogleOAuthClientConfig\n) {\n if (!clientId || !clientSecret) {\n throw new Error('Google OAuth credentials not configured')\n }\n\n return new OAuth2Client(clientId, clientSecret, redirectUri)\n}\n\nfunction loginErrorRedirect(siteUrl: string, error: string) {\n return NextResponse.redirect(\n `${siteUrl}${GOOGLE_OAUTH_LOGIN_PATH}?error=${encodeURIComponent(error)}`\n )\n}\n\n/** GET `/api/auth/google` — start the Google OAuth redirect. */\nexport async function handleGoogleOAuthStart(clientConfig: GoogleOAuthClientConfig) {\n try {\n const siteUrl = getSiteUrl()\n const redirectUri = `${siteUrl}${GOOGLE_OAUTH_CALLBACK_PATH}`\n const client = getGoogleOAuthClient(redirectUri, clientConfig)\n\n const authorizeUrl = client.generateAuthUrl({\n access_type: 'offline',\n scope: [...GOOGLE_OAUTH_SCOPES],\n prompt: 'select_account',\n })\n\n return NextResponse.redirect(authorizeUrl)\n } catch (error) {\n log.error(error instanceof Error ? error.message : 'OAuth configuration error')\n return NextResponse.json({ error: 'OAuth configuration error' }, { status: 500 })\n }\n}\n\n/** GET `/api/auth/google/callback` — exchange the code and set the Payload session cookie. */\nexport async function handleGoogleOAuthCallback(\n request: NextRequest,\n { config, ...clientConfig }: HandleGoogleOAuthCallbackOptions\n) {\n try {\n const { searchParams } = new URL(request.url)\n const siteUrl = getSiteUrl()\n const code = searchParams.get('code')\n const error = searchParams.get('error')\n\n if (error) {\n return loginErrorRedirect(siteUrl, error)\n }\n\n if (!code) {\n return loginErrorRedirect(siteUrl, 'no_code')\n }\n\n const redirectUri = `${siteUrl}${GOOGLE_OAUTH_CALLBACK_PATH}`\n const client = getGoogleOAuthClient(redirectUri, clientConfig)\n\n const { tokens } = await client.getToken(code)\n client.setCredentials(tokens)\n\n if (!tokens.id_token) {\n return loginErrorRedirect(siteUrl, 'no_token')\n }\n\n const ticket = await client.verifyIdToken({\n idToken: tokens.id_token,\n audience: clientConfig.clientId,\n })\n\n const googlePayload = ticket.getPayload()\n if (!googlePayload?.email) {\n return loginErrorRedirect(siteUrl, 'no_email')\n }\n\n const { email, sub: googleId, name, picture } = googlePayload\n const payload = await getPayload({\n config: (await config) as SanitizedConfig,\n })\n\n const users = await payload.find({\n collection: 'users',\n where: {\n or: [{ email: { equals: email } }, { googleId: { equals: googleId } }],\n },\n limit: 1,\n })\n\n let user = users.docs[0]\n if (users.docs.length > 0) {\n user = users.docs[0]\n\n const googleUser = user as typeof user & {\n googleId?: string | null\n name?: string | null\n picture?: string | null\n }\n const updateData: Record<string, unknown> = {}\n\n if (!googleUser.googleId) {\n updateData.googleId = googleId\n updateData.name = name || googleUser.name\n updateData.picture = picture || googleUser.picture\n }\n\n if (Object.keys(updateData).length > 0) {\n user = await payload.update({\n collection: 'users',\n id: user.id,\n data: updateData,\n })\n }\n } else {\n return loginErrorRedirect(siteUrl, 'user_not_found')\n }\n\n const collectionConfig = payload.collections['users'].config\n\n const sessionUser = user as Parameters<typeof addSessionToUser>[0]['user']\n\n const { sid } = await addSessionToUser({\n collectionConfig,\n payload,\n req: {\n payload,\n user: sessionUser,\n } as unknown as Parameters<typeof addSessionToUser>[0]['req'],\n user: sessionUser,\n })\n\n const fieldsToSign = getFieldsToSign({\n collectionConfig,\n email: sessionUser.email as string,\n sid,\n user: sessionUser,\n })\n\n const tokenExpiration = collectionConfig.auth.tokenExpiration || 7200\n const secret = payload.secret\n\n const { token } = await jwtSign({\n fieldsToSign,\n secret,\n tokenExpiration,\n })\n\n const cookie = generatePayloadCookie({\n collectionAuthConfig: collectionConfig.auth,\n cookiePrefix: payload.config.cookiePrefix || 'payload',\n token,\n returnCookieAsObject: true,\n })\n\n const response = NextResponse.redirect(`${siteUrl}${GOOGLE_OAUTH_ADMIN_PATH}`)\n\n response.cookies.set(cookie.name, cookie.value!, {\n httpOnly: cookie.httpOnly,\n sameSite: cookie.sameSite?.toLowerCase() as 'strict' | 'lax' | 'none' | undefined,\n secure: cookie.secure,\n path: cookie.path,\n expires: cookie.expires ? new Date(cookie.expires) : undefined,\n maxAge: cookie.maxAge,\n })\n\n return response\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : 'Authentication failed'\n log.error(errorMessage)\n return loginErrorRedirect(getSiteUrl(), errorMessage)\n }\n}\n"],"mappings":"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"}
|
package/dist/cms/index.d.mts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { A as
|
|
1
|
+
import { $ as createAccessHelpers, A as downloadCtaBlock, B as seoFields, C as codeBlockLanguages, D as sectionHeadingBlock, E as leafBlocks, F as richTextBlock, G as STOCK_COLLECTION_SLUGS, H as BuildCmsConfigOptions, I as coerceBlogPostCategoriesOnRead, J as GlobalOverride, K as StockCollectionSlug, L as coerceBlogPostCategoriesOnWrite, M as imageBlock, N as codeTabsBlock, O as accordionBlock, P as codeBlock, Q as AccessOptions, R as setPublishedAt, S as showcaseContentBlocks, T as languageOptions, U as buildCmsConfig, V as draftsWithAutosave, W as Overrides, X as applyGlobalOverride, Y as applyCollectionOverride, Z as AccessHelpers, _ as requireEnvAtRuntime, a as createChangelog, b as blogContentBlocks, c as createPages, d as createAuthors, f as createMedia, g as createS3MediaPlugin, h as CreateS3MediaPluginOptions, i as createEventTags, j as videoBlock, k as stepsBlock, l as createBlogPosts, m as createUsers, n as createSiteSettings, o as createChangelogTags, p as createApiTokens, q as CollectionOverride, r as createEvents, s as createLegalPages, t as createAnnouncementBanner, u as createCategories, v as getRichTextEditorConfig, w as defaultCodeLanguage, x as pageContentBlocks, y as richTextEditor, z as slugify } from "../index.mjs";
|
|
2
2
|
import { n as readPayloadMediaAsDataUrl, r as readPayloadMediaBuffer, t as PayloadMedia } from "../readMediaBuffer.mjs";
|
|
3
|
-
export { type AccessHelpers, type AccessOptions, type BuildCmsConfigOptions, type CollectionOverride, type CreateS3MediaPluginOptions, type GlobalOverride, type Overrides, type PayloadMedia, STOCK_COLLECTION_SLUGS, type StockCollectionSlug, accordionBlock, applyCollectionOverride, applyGlobalOverride, blogContentBlocks, buildCmsConfig, codeBlock, codeBlockLanguages, codeTabsBlock, createAccessHelpers, createApiTokens, createAuthors, createBlogPosts, createCategories, createChangelog, createChangelogTags, createLegalPages, createMedia, createPages, createS3MediaPlugin, createSiteSettings, createUsers, defaultCodeLanguage, downloadCtaBlock, draftsWithAutosave, getRichTextEditorConfig, imageBlock, languageOptions, leafBlocks, pageContentBlocks, readPayloadMediaAsDataUrl, readPayloadMediaBuffer, requireEnvAtRuntime, richTextBlock, richTextEditor, sectionHeadingBlock, seoFields, setPublishedAt, showcaseContentBlocks, slugify, stepsBlock, videoBlock };
|
|
3
|
+
export { type AccessHelpers, type AccessOptions, type BuildCmsConfigOptions, type CollectionOverride, type CreateS3MediaPluginOptions, type GlobalOverride, type Overrides, type PayloadMedia, STOCK_COLLECTION_SLUGS, type StockCollectionSlug, accordionBlock, applyCollectionOverride, applyGlobalOverride, blogContentBlocks, buildCmsConfig, codeBlock, codeBlockLanguages, codeTabsBlock, coerceBlogPostCategoriesOnRead, coerceBlogPostCategoriesOnWrite, createAccessHelpers, createAnnouncementBanner, createApiTokens, createAuthors, createBlogPosts, createCategories, createChangelog, createChangelogTags, createEventTags, createEvents, createLegalPages, createMedia, createPages, createS3MediaPlugin, createSiteSettings, createUsers, defaultCodeLanguage, downloadCtaBlock, draftsWithAutosave, getRichTextEditorConfig, imageBlock, languageOptions, leafBlocks, pageContentBlocks, readPayloadMediaAsDataUrl, readPayloadMediaBuffer, requireEnvAtRuntime, richTextBlock, richTextEditor, sectionHeadingBlock, seoFields, setPublishedAt, showcaseContentBlocks, slugify, stepsBlock, videoBlock };
|
package/dist/cms/index.mjs
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { A as
|
|
1
|
+
import { A as richTextBlock, B as languageOptions, C as blogContentBlocks, D as accordionBlock, E as sectionHeadingBlock, F as downloadCtaBlock, G as createAccessHelpers, H as seoFields, I as codeTabsBlock, L as codeBlock, M as richTextEditor, N as videoBlock, O as leafBlocks, P as imageBlock, R as codeBlockLanguages, S as coerceBlogPostCategoriesOnWrite, T as showcaseContentBlocks, U as slugify, V as createAuthors, W as createApiTokens, _ as createCategories, a as STOCK_COLLECTION_SLUGS, b as setPublishedAt, c as createSiteSettings, d as createPages, f as createMedia, g as createChangelog, h as createChangelogTags, i as buildCmsConfig, j as getRichTextEditorConfig, k as stepsBlock, l as createUsers, m as createEventTags, n as createS3MediaPlugin, o as applyCollectionOverride, p as createEvents, r as requireEnvAtRuntime, s as applyGlobalOverride, t as createAnnouncementBanner, u as createLegalPages, v as createBlogPosts, w as pageContentBlocks, x as coerceBlogPostCategoriesOnRead, y as draftsWithAutosave, z as defaultCodeLanguage } from "../cms.mjs";
|
|
2
2
|
import { n as readPayloadMediaBuffer, t as readPayloadMediaAsDataUrl } from "../readMediaBuffer.mjs";
|
|
3
|
-
export { STOCK_COLLECTION_SLUGS, accordionBlock, applyCollectionOverride, applyGlobalOverride, blogContentBlocks, buildCmsConfig, codeBlock, codeBlockLanguages, codeTabsBlock, createAccessHelpers, createApiTokens, createAuthors, createBlogPosts, createCategories, createChangelog, createChangelogTags, createLegalPages, createMedia, createPages, createS3MediaPlugin, createSiteSettings, createUsers, defaultCodeLanguage, downloadCtaBlock, draftsWithAutosave, getRichTextEditorConfig, imageBlock, languageOptions, leafBlocks, pageContentBlocks, readPayloadMediaAsDataUrl, readPayloadMediaBuffer, requireEnvAtRuntime, richTextBlock, richTextEditor, sectionHeadingBlock, seoFields, setPublishedAt, showcaseContentBlocks, slugify, stepsBlock, videoBlock };
|
|
3
|
+
export { STOCK_COLLECTION_SLUGS, accordionBlock, applyCollectionOverride, applyGlobalOverride, blogContentBlocks, buildCmsConfig, codeBlock, codeBlockLanguages, codeTabsBlock, coerceBlogPostCategoriesOnRead, coerceBlogPostCategoriesOnWrite, createAccessHelpers, createAnnouncementBanner, createApiTokens, createAuthors, createBlogPosts, createCategories, createChangelog, createChangelogTags, createEventTags, createEvents, createLegalPages, createMedia, createPages, createS3MediaPlugin, createSiteSettings, createUsers, defaultCodeLanguage, downloadCtaBlock, draftsWithAutosave, getRichTextEditorConfig, imageBlock, languageOptions, leafBlocks, pageContentBlocks, readPayloadMediaAsDataUrl, readPayloadMediaBuffer, requireEnvAtRuntime, richTextBlock, richTextEditor, sectionHeadingBlock, seoFields, setPublishedAt, showcaseContentBlocks, slugify, stepsBlock, videoBlock };
|