@miguelmorales13/nestkit 0.5.1 → 0.6.0

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.
@@ -0,0 +1,610 @@
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } async function _asyncNullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return await rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }
2
+
3
+
4
+ var _chunkNVCI3CQIcjs = require('../../chunk-NVCI3CQI.cjs');
5
+
6
+
7
+
8
+ var _chunkFDNGAYTZcjs = require('../../chunk-FDNGAYTZ.cjs');
9
+ require('../../chunk-R7BVS6CI.cjs');
10
+
11
+
12
+
13
+ var _chunk2REOCMUDcjs = require('../../chunk-2REOCMUD.cjs');
14
+
15
+ // src/auth/oauth/oauth-provider.ts
16
+ async function readJson(response, what) {
17
+ const text = await response.text();
18
+ let body;
19
+ try {
20
+ body = JSON.parse(text);
21
+ } catch (e) {
22
+ throw new Error(`${what}: expected JSON, got ${response.status} ${text.slice(0, 200)}`);
23
+ }
24
+ if (!response.ok) {
25
+ throw new Error(`${what}: HTTP ${response.status} ${text.slice(0, 200)}`);
26
+ }
27
+ return body;
28
+ }
29
+ function googleProvider(credentials = {}) {
30
+ return {
31
+ name: "google",
32
+ clientId: _nullishCoalesce(credentials.clientId, () => ( process.env.GOOGLE_CLIENT_ID)),
33
+ clientSecret: _nullishCoalesce(credentials.clientSecret, () => ( process.env.GOOGLE_CLIENT_SECRET)),
34
+ callbackUrl: _nullishCoalesce(credentials.callbackUrl, () => ( process.env.GOOGLE_CALLBACK_URL)),
35
+ authorizeUrl: "https://accounts.google.com/o/oauth2/v2/auth",
36
+ tokenUrl: "https://oauth2.googleapis.com/token",
37
+ scope: _nullishCoalesce(credentials.scope, () => ( "email profile")),
38
+ authorizeParams: {
39
+ // Without this, someone signed into several Google accounts is put
40
+ // straight back into the one the browser last used, with no way to pick
41
+ // — which reads as the app ignoring who they wanted to be.
42
+ prompt: "select_account",
43
+ access_type: "offline"
44
+ },
45
+ async fetchProfile(accessToken) {
46
+ const data = await readJson(
47
+ await fetch("https://www.googleapis.com/oauth2/v3/userinfo", {
48
+ headers: { Authorization: `Bearer ${accessToken}` }
49
+ }),
50
+ "Google userinfo"
51
+ );
52
+ const email = data.email;
53
+ if (!email) throw new Error("Google did not return an email.");
54
+ return {
55
+ providerAccountId: String(data.sub),
56
+ email,
57
+ name: data.name,
58
+ avatarUrl: data.picture
59
+ };
60
+ }
61
+ };
62
+ }
63
+ function facebookProvider(credentials = {}) {
64
+ const version = _nullishCoalesce(process.env.FACEBOOK_GRAPH_VERSION, () => ( "v21.0"));
65
+ return {
66
+ name: "facebook",
67
+ clientId: _nullishCoalesce(credentials.clientId, () => ( process.env.FACEBOOK_APP_ID)),
68
+ clientSecret: _nullishCoalesce(credentials.clientSecret, () => ( process.env.FACEBOOK_APP_SECRET)),
69
+ callbackUrl: _nullishCoalesce(credentials.callbackUrl, () => ( process.env.FACEBOOK_CALLBACK_URL)),
70
+ authorizeUrl: `https://www.facebook.com/${version}/dialog/oauth`,
71
+ tokenUrl: `https://graph.facebook.com/${version}/oauth/access_token`,
72
+ scope: _nullishCoalesce(credentials.scope, () => ( "email")),
73
+ async fetchProfile(accessToken) {
74
+ const url = new URL(`https://graph.facebook.com/${version}/me`);
75
+ url.searchParams.set("fields", "id,name,email,picture.type(large)");
76
+ const data = await readJson(
77
+ await fetch(url, { headers: { Authorization: `Bearer ${accessToken}` } }),
78
+ "Facebook me"
79
+ );
80
+ const picture = data.picture;
81
+ return {
82
+ providerAccountId: String(data.id),
83
+ email: _nullishCoalesce(data.email, () => ( "")),
84
+ name: data.name,
85
+ avatarUrl: _optionalChain([picture, 'optionalAccess', _ => _.data, 'optionalAccess', _2 => _2.url])
86
+ };
87
+ }
88
+ };
89
+ }
90
+ function buildAuthorizeUrl(provider, state) {
91
+ const params = new URLSearchParams({
92
+ client_id: _nullishCoalesce(provider.clientId, () => ( "")),
93
+ redirect_uri: _nullishCoalesce(provider.callbackUrl, () => ( "")),
94
+ response_type: "code",
95
+ scope: provider.scope,
96
+ ...provider.authorizeParams,
97
+ ...state ? { state } : {}
98
+ });
99
+ return `${provider.authorizeUrl}?${params.toString()}`;
100
+ }
101
+ async function exchangeCodeForToken(provider, code) {
102
+ const body = new URLSearchParams({
103
+ client_id: _nullishCoalesce(provider.clientId, () => ( "")),
104
+ client_secret: _nullishCoalesce(provider.clientSecret, () => ( "")),
105
+ redirect_uri: _nullishCoalesce(provider.callbackUrl, () => ( "")),
106
+ grant_type: "authorization_code",
107
+ code
108
+ });
109
+ const data = await readJson(
110
+ await fetch(provider.tokenUrl, {
111
+ method: "POST",
112
+ headers: {
113
+ "Content-Type": "application/x-www-form-urlencoded",
114
+ Accept: "application/json"
115
+ },
116
+ body
117
+ }),
118
+ `${provider.name} token exchange`
119
+ );
120
+ const token = data.access_token;
121
+ if (!token) throw new Error(`${provider.name} token exchange returned no access_token.`);
122
+ return token;
123
+ }
124
+
125
+ // src/auth/oauth/oauth.options.ts
126
+ var OAUTH_AUTH_OPTIONS = /* @__PURE__ */ Symbol("OAUTH_AUTH_OPTIONS");
127
+ var OAUTH_STORE = /* @__PURE__ */ Symbol("OAUTH_STORE");
128
+ var REFRESH_TOKEN_STORE = /* @__PURE__ */ Symbol("REFRESH_TOKEN_STORE");
129
+ function resolveOAuthOptions(options) {
130
+ const webUrl = options.webUrl.replace(/\/$/, "");
131
+ return {
132
+ providers: options.providers,
133
+ webUrl,
134
+ onLoginRedirect: _nullishCoalesce(options.onLoginRedirect, () => ( ((accessToken) => `${webUrl}/oauth-callback#accessToken=${accessToken}`))),
135
+ onErrorRedirect: _nullishCoalesce(options.onErrorRedirect, () => ( ((message) => `${webUrl}/login?error=${encodeURIComponent(message)}`))),
136
+ onLinkRedirect: _nullishCoalesce(options.onLinkRedirect, () => ( ((provider, error) => error ? `${webUrl}/profile?error=${encodeURIComponent(error)}` : `${webUrl}/profile?linked=${provider}`))),
137
+ cookie: {
138
+ name: _nullishCoalesce(_optionalChain([options, 'access', _3 => _3.cookie, 'optionalAccess', _4 => _4.name]), () => ( "refresh_token")),
139
+ path: _nullishCoalesce(_optionalChain([options, 'access', _5 => _5.cookie, 'optionalAccess', _6 => _6.path]), () => ( "/auth")),
140
+ sameSite: _nullishCoalesce(_optionalChain([options, 'access', _7 => _7.cookie, 'optionalAccess', _8 => _8.sameSite]), () => ( "lax")),
141
+ secure: _nullishCoalesce(_optionalChain([options, 'access', _9 => _9.cookie, 'optionalAccess', _10 => _10.secure]), () => ( process.env.NODE_ENV === "production"))
142
+ },
143
+ linkCookieName: _nullishCoalesce(options.linkCookieName, () => ( "link_intent")),
144
+ accessTtlSeconds: _nullishCoalesce(options.accessTtlSeconds, () => ( 15 * 60)),
145
+ refreshTtlSeconds: _nullishCoalesce(options.refreshTtlSeconds, () => ( 30 * 24 * 60 * 60)),
146
+ linkTtlSeconds: _nullishCoalesce(options.linkTtlSeconds, () => ( 10 * 60)),
147
+ accessSecret: options.accessSecret,
148
+ refreshSecret: options.refreshSecret,
149
+ singleSession: _nullishCoalesce(options.singleSession, () => ( true)),
150
+ syncProfile: _nullishCoalesce(options.syncProfile, () => ( ((_user, profile) => ({ name: profile.name, avatarUrl: _nullishCoalesce(profile.avatarUrl, () => ( null)) })))),
151
+ mapUser: options.mapUser,
152
+ unavailableMessage: _nullishCoalesce(options.unavailableMessage, () => ( ((provider) => `Signing in with ${provider} is unavailable.`))),
153
+ genericErrorMessage: _nullishCoalesce(options.genericErrorMessage, () => ( (() => "Sign-in could not be completed. Please try again."))),
154
+ missingEmailMessage: _nullishCoalesce(options.missingEmailMessage, () => ( ((provider) => `${provider} did not share your email address, so we cannot create your account.`)))
155
+ };
156
+ }
157
+
158
+ // src/auth/oauth/oauth-auth.service.ts
159
+ var _common = require('@nestjs/common');
160
+ var _crypto = require('crypto');
161
+ var _jsonwebtoken = require('jsonwebtoken'); var _jsonwebtoken2 = _interopRequireDefault(_jsonwebtoken);
162
+ function hashToken(token) {
163
+ return _crypto.createHash.call(void 0, "sha256").update(token).digest("hex");
164
+ }
165
+ function requireSecret(explicit, envName) {
166
+ const value = _nullishCoalesce(explicit, () => ( process.env[envName]));
167
+ if (!value) throw new Error(`OAuthAuthService: ${envName} is not set`);
168
+ return value;
169
+ }
170
+ var OAuthAuthService = class {
171
+ constructor(options, store, sessions) {
172
+ this.store = store;
173
+ this.sessions = sessions;
174
+ this.options = resolveOAuthOptions(options);
175
+ }
176
+ get config() {
177
+ return this.options;
178
+ }
179
+ /**
180
+ * Looks up a configured provider, or explains that it is unavailable.
181
+ *
182
+ * Missing credentials make one provider stop existing rather than stopping
183
+ * the boot — the same fail-late rule the rest of nestkit's optional
184
+ * integrations follow. An unknown name and an unconfigured one answer
185
+ * identically on purpose: neither is something the caller can act on
186
+ * differently, and distinguishing them only tells a prober what exists.
187
+ */
188
+ provider(name) {
189
+ const found = this.options.providers.find((p) => p.name === name);
190
+ if (!_optionalChain([found, 'optionalAccess', _11 => _11.clientId]) || !found.clientSecret) {
191
+ throw new (0, _common.ServiceUnavailableException)(this.options.unavailableMessage(name));
192
+ }
193
+ return found;
194
+ }
195
+ authorizeUrl(name) {
196
+ return buildAuthorizeUrl(this.provider(name));
197
+ }
198
+ /** Exchanges the callback's `code` for a normalised profile. */
199
+ async profileFromCode(name, code) {
200
+ const provider = this.provider(name);
201
+ const accessToken = await exchangeCodeForToken(provider, code);
202
+ const profile = await provider.fetchProfile(accessToken);
203
+ if (!profile.email) {
204
+ throw new (0, _chunkFDNGAYTZcjs.UnauthorizedAppException)(this.options.missingEmailMessage(name));
205
+ }
206
+ return { ...profile, email: normaliseEmail(profile.email) };
207
+ }
208
+ /**
209
+ * Signs someone in, creating the account the first time.
210
+ *
211
+ * The provider is a parameter because the flow is identical for all of
212
+ * them: find the linked account, and failing that link by email to whoever
213
+ * is already registered. That email link is what makes signing in with
214
+ * Facebook today and Google tomorrow land on one account instead of two.
215
+ */
216
+ async login(providerName, profile) {
217
+ const provider = this.provider(providerName).name;
218
+ const existing = await this.store.findAccount(provider, profile.providerAccountId);
219
+ let user = _nullishCoalesce(_optionalChain([existing, 'optionalAccess', _12 => _12.user]), () => ( null));
220
+ if (!user) {
221
+ const email = normaliseEmail(profile.email);
222
+ user = await _asyncNullishCoalesce(await this.store.findUserByEmail(email), async () => ( await this.store.createUser({
223
+ email,
224
+ name: profile.name,
225
+ avatarUrl: profile.avatarUrl
226
+ })));
227
+ await this.store.createAccount({
228
+ userId: user.id,
229
+ provider,
230
+ providerAccountId: profile.providerAccountId,
231
+ avatarUrl: _nullishCoalesce(profile.avatarUrl, () => ( null))
232
+ });
233
+ } else {
234
+ await this.store.updateAccountAvatar(
235
+ provider,
236
+ profile.providerAccountId,
237
+ _nullishCoalesce(profile.avatarUrl, () => ( null))
238
+ );
239
+ }
240
+ const changes = this.options.syncProfile(user, profile);
241
+ if (changes && (changes.name !== user.name || changes.avatarUrl !== user.avatarUrl)) {
242
+ user = await this.store.updateUser(user.id, changes);
243
+ }
244
+ if (this.options.singleSession) {
245
+ await this.sessions.revokeAllForUser(user.id);
246
+ }
247
+ const tokens = await this.issueTokenPair(user);
248
+ return { user: this.present(user), ...tokens };
249
+ }
250
+ /**
251
+ * Rotates a refresh token: the old one is spent, a new pair comes back.
252
+ *
253
+ * Revoking before issuing, and only continuing when the revoke actually hit
254
+ * a live row, is the whole point — replaying a token that has already been
255
+ * used finds nothing to revoke and is rejected.
256
+ */
257
+ async refresh(rawRefreshToken) {
258
+ const payload = this.verifyRefresh(rawRefreshToken);
259
+ const { count } = await this.sessions.revokeIfActive(hashToken(rawRefreshToken));
260
+ if (count === 0) throw new (0, _chunkFDNGAYTZcjs.UnauthorizedAppException)("Invalid refresh token.");
261
+ const user = await this.store.findUserById(payload.sub);
262
+ if (!user) throw new (0, _chunkFDNGAYTZcjs.UnauthorizedAppException)("Invalid refresh token.");
263
+ return this.issueTokenPair(user);
264
+ }
265
+ async logout(rawRefreshToken) {
266
+ await this.sessions.revokeIfActive(hashToken(rawRefreshToken));
267
+ }
268
+ async currentUser(userId) {
269
+ const user = await this.store.findUserById(userId);
270
+ if (!user) throw new (0, _chunkFDNGAYTZcjs.UnauthorizedAppException)("User not found.");
271
+ return this.present(user);
272
+ }
273
+ /**
274
+ * Issues the short-lived permission to link another provider.
275
+ *
276
+ * Needed because the OAuth leg is a whole-browser navigation, not a request
277
+ * with headers: the access token does not travel there. So a permission is
278
+ * minted, parked in an httpOnly cookie, and read back by the callback to
279
+ * tell "link this to me" from "sign in". It carries its own `typ` so it can
280
+ * never pass as an access token, or the other way round, despite sharing a
281
+ * secret.
282
+ */
283
+ issueLinkToken(userId) {
284
+ const ttl = this.options.linkTtlSeconds;
285
+ const token = _jsonwebtoken2.default.sign(
286
+ { sub: userId, typ: "link" },
287
+ requireSecret(this.options.accessSecret, "JWT_ACCESS_SECRET"),
288
+ { expiresIn: ttl }
289
+ );
290
+ return { token, expiresAt: new Date(Date.now() + ttl * 1e3) };
291
+ }
292
+ /**
293
+ * The user id behind a link permission, or null.
294
+ *
295
+ * Null rather than an exception: with no valid permission the callback just
296
+ * performs an ordinary sign-in, which is the correct outcome.
297
+ */
298
+ readLinkToken(token) {
299
+ if (!token) return null;
300
+ try {
301
+ const payload = _jsonwebtoken2.default.verify(
302
+ token,
303
+ requireSecret(this.options.accessSecret, "JWT_ACCESS_SECRET")
304
+ );
305
+ return payload.typ === "link" && payload.sub ? payload.sub : null;
306
+ } catch (e2) {
307
+ return null;
308
+ }
309
+ }
310
+ /**
311
+ * Ties a provider account to the already-signed-in user.
312
+ *
313
+ * This is the way out when the emails do not match: if someone's Facebook
314
+ * is registered under a different address, linking by email can never join
315
+ * them, and without this they keep two profiles forever.
316
+ */
317
+ async linkAccount(userId, providerName, profile) {
318
+ const provider = this.provider(providerName).name;
319
+ const existing = await this.store.findAccount(provider, profile.providerAccountId);
320
+ if (existing && existing.userId !== userId) {
321
+ throw new (0, _chunkFDNGAYTZcjs.ValidationAppException)(
322
+ "That account is already connected to another profile.",
323
+ { provider },
324
+ "OAUTH_ACCOUNT_TAKEN"
325
+ );
326
+ }
327
+ const alreadyLinked = await this.store.findAccountByUserAndProvider(userId, provider);
328
+ if (alreadyLinked && alreadyLinked.providerAccountId !== profile.providerAccountId) {
329
+ throw new (0, _chunkFDNGAYTZcjs.ValidationAppException)(
330
+ "You already have an account from that provider connected.",
331
+ { provider },
332
+ "OAUTH_PROVIDER_ALREADY_LINKED"
333
+ );
334
+ }
335
+ if (existing) {
336
+ await this.store.updateAccountAvatar(
337
+ provider,
338
+ profile.providerAccountId,
339
+ _nullishCoalesce(profile.avatarUrl, () => ( null))
340
+ );
341
+ return;
342
+ }
343
+ await this.store.createAccount({
344
+ userId,
345
+ provider,
346
+ providerAccountId: profile.providerAccountId,
347
+ avatarUrl: _nullishCoalesce(profile.avatarUrl, () => ( null))
348
+ });
349
+ }
350
+ /**
351
+ * Which providers are linked, and what photo each one offers.
352
+ *
353
+ * Enough to build both a "connect another account" screen and an avatar
354
+ * picker; what the app does with the choice is the app's decision.
355
+ */
356
+ async linkedAccounts(userId) {
357
+ const accounts = await this.store.listAccountsByUser(userId);
358
+ return {
359
+ connected: accounts.map((a) => a.provider),
360
+ avatars: accounts.filter((a) => Boolean(a.avatarUrl)).map((a) => ({ provider: a.provider, avatarUrl: a.avatarUrl }))
361
+ };
362
+ }
363
+ present(user) {
364
+ return this.options.mapUser ? this.options.mapUser(user) : user;
365
+ }
366
+ async issueTokenPair(user) {
367
+ const accessToken = _jsonwebtoken2.default.sign(
368
+ { sub: user.id, email: user.email },
369
+ requireSecret(this.options.accessSecret, "JWT_ACCESS_SECRET"),
370
+ { expiresIn: this.options.accessTtlSeconds }
371
+ );
372
+ const refreshToken = _jsonwebtoken2.default.sign(
373
+ // The jti keeps two tokens minted for the same user in the same second
374
+ // from coming out byte-identical — a JWT is deterministic given the
375
+ // same payload and secret, and identical tokens collide on the unique
376
+ // index over the stored hash.
377
+ { sub: user.id, jti: _crypto.randomUUID.call(void 0, ) },
378
+ requireSecret(this.options.refreshSecret, "JWT_REFRESH_SECRET"),
379
+ { expiresIn: this.options.refreshTtlSeconds }
380
+ );
381
+ const expiresAt = new Date(Date.now() + this.options.refreshTtlSeconds * 1e3);
382
+ await this.sessions.create(user.id, hashToken(refreshToken), expiresAt);
383
+ return { accessToken, refreshToken, refreshTokenExpiresAt: expiresAt };
384
+ }
385
+ verifyRefresh(token) {
386
+ try {
387
+ return _jsonwebtoken2.default.verify(
388
+ token,
389
+ requireSecret(this.options.refreshSecret, "JWT_REFRESH_SECRET")
390
+ );
391
+ } catch (e3) {
392
+ throw new (0, _chunkFDNGAYTZcjs.UnauthorizedAppException)("Invalid refresh token.");
393
+ }
394
+ }
395
+ };
396
+ OAuthAuthService = exports.OAuthAuthService = _chunk2REOCMUDcjs.__decorateClass.call(void 0, [
397
+ _common.Injectable.call(void 0, ),
398
+ _chunk2REOCMUDcjs.__decorateParam.call(void 0, 0, _common.Inject.call(void 0, OAUTH_AUTH_OPTIONS)),
399
+ _chunk2REOCMUDcjs.__decorateParam.call(void 0, 1, _common.Inject.call(void 0, OAUTH_STORE)),
400
+ _chunk2REOCMUDcjs.__decorateParam.call(void 0, 2, _common.Inject.call(void 0, REFRESH_TOKEN_STORE))
401
+ ], OAuthAuthService);
402
+ function normaliseEmail(email) {
403
+ return email.trim().toLowerCase();
404
+ }
405
+
406
+ // src/auth/oauth/oauth-callback.filter.ts
407
+
408
+
409
+
410
+
411
+
412
+
413
+
414
+ // src/auth/oauth/http.ts
415
+ function setCookie(reply, name, value, options) {
416
+ if (typeof reply.setCookie === "function") reply.setCookie(name, value, options);
417
+ else if (typeof reply.cookie === "function") reply.cookie(name, value, options);
418
+ else throw new Error("No cookie support on the reply: register @fastify/cookie or cookie-parser.");
419
+ }
420
+ function clearCookie(reply, name, options) {
421
+ _optionalChain([reply, 'access', _13 => _13.clearCookie, 'optionalCall', _14 => _14(name, options)]);
422
+ }
423
+ function redirect(reply, url) {
424
+ reply.redirect(url, 302);
425
+ }
426
+
427
+ // src/auth/oauth/oauth-callback.filter.ts
428
+ var OAuthCallbackFilter = class {
429
+ constructor(options) {
430
+ this.logger = new (0, _common.Logger)(OAuthCallbackFilter.name);
431
+ this.options = resolveOAuthOptions(options);
432
+ }
433
+ catch(exception, host) {
434
+ const reply = host.switchToHttp().getResponse();
435
+ this.logger.error(
436
+ `OAuth callback failed: ${exception instanceof Error ? exception.stack : String(exception)}`
437
+ );
438
+ const message = exception instanceof _common.HttpException ? exception.message : this.options.genericErrorMessage();
439
+ redirect(reply, this.options.onErrorRedirect(message));
440
+ }
441
+ };
442
+ OAuthCallbackFilter = exports.OAuthCallbackFilter = _chunk2REOCMUDcjs.__decorateClass.call(void 0, [
443
+ _common.Catch.call(void 0, ),
444
+ _chunk2REOCMUDcjs.__decorateParam.call(void 0, 0, _common.Inject.call(void 0, OAUTH_AUTH_OPTIONS))
445
+ ], OAuthCallbackFilter);
446
+
447
+ // src/auth/oauth/oauth.controller.ts
448
+
449
+
450
+
451
+
452
+
453
+
454
+
455
+
456
+
457
+
458
+
459
+ function createOAuthController(options = {}) {
460
+ const Guard = _nullishCoalesce(options.guard, () => ( _chunkNVCI3CQIcjs.JwtAuthGuard));
461
+ const userId = _nullishCoalesce(options.userId, () => ( ((user) => user.sub)));
462
+ let OAuthControllerHost = class {
463
+ constructor(auth) {
464
+ this.auth = auth;
465
+ }
466
+ async refresh(request, reply) {
467
+ const raw = _optionalChain([request, 'access', _15 => _15.cookies, 'optionalAccess', _16 => _16[this.auth.config.cookie.name]]);
468
+ if (!raw) throw new (0, _chunkFDNGAYTZcjs.UnauthorizedAppException)("No refresh token.");
469
+ const { accessToken, refreshToken, refreshTokenExpiresAt } = await this.auth.refresh(raw);
470
+ this.writeRefreshCookie(reply, refreshToken, refreshTokenExpiresAt);
471
+ return { accessToken };
472
+ }
473
+ async logout(request, reply) {
474
+ const raw = _optionalChain([request, 'access', _17 => _17.cookies, 'optionalAccess', _18 => _18[this.auth.config.cookie.name]]);
475
+ if (raw) await this.auth.logout(raw);
476
+ clearCookie(reply, this.auth.config.cookie.name, { path: this.auth.config.cookie.path });
477
+ return { success: true };
478
+ }
479
+ me(user) {
480
+ return this.auth.currentUser(userId(user));
481
+ }
482
+ linkedAccounts(user) {
483
+ return this.auth.linkedAccounts(userId(user));
484
+ }
485
+ linkIntent(user, reply) {
486
+ const { token, expiresAt } = this.auth.issueLinkToken(userId(user));
487
+ setCookie(reply, this.auth.config.linkCookieName, token, {
488
+ ...this.cookieBase(),
489
+ expires: expiresAt
490
+ });
491
+ return { ok: true };
492
+ }
493
+ start(provider, reply) {
494
+ redirect(reply, this.auth.authorizeUrl(provider));
495
+ }
496
+ async callback(provider, code, request, reply) {
497
+ if (!code) throw new (0, _chunkFDNGAYTZcjs.UnauthorizedAppException)(this.auth.config.genericErrorMessage());
498
+ const profile = await this.auth.profileFromCode(provider, code);
499
+ const linkUserId = this.auth.readLinkToken(_optionalChain([request, 'access', _19 => _19.cookies, 'optionalAccess', _20 => _20[this.auth.config.linkCookieName]]));
500
+ if (linkUserId) {
501
+ clearCookie(reply, this.auth.config.linkCookieName, { path: this.auth.config.cookie.path });
502
+ try {
503
+ await this.auth.linkAccount(linkUserId, provider, profile);
504
+ redirect(reply, this.auth.config.onLinkRedirect(provider));
505
+ } catch (error) {
506
+ const message = error instanceof Error ? error.message : "Could not connect the account.";
507
+ redirect(reply, this.auth.config.onLinkRedirect(provider, message));
508
+ }
509
+ return;
510
+ }
511
+ const { accessToken, refreshToken, refreshTokenExpiresAt } = await this.auth.login(
512
+ provider,
513
+ profile
514
+ );
515
+ this.writeRefreshCookie(reply, refreshToken, refreshTokenExpiresAt);
516
+ redirect(reply, this.auth.config.onLoginRedirect(accessToken));
517
+ }
518
+ cookieBase() {
519
+ const { secure, sameSite, path } = this.auth.config.cookie;
520
+ return { httpOnly: true, secure, sameSite, path };
521
+ }
522
+ writeRefreshCookie(reply, token, expiresAt) {
523
+ setCookie(reply, this.auth.config.cookie.name, token, {
524
+ ...this.cookieBase(),
525
+ expires: expiresAt
526
+ });
527
+ }
528
+ };
529
+ _chunk2REOCMUDcjs.__decorateClass.call(void 0, [
530
+ _common.Post.call(void 0, "refresh"),
531
+ _chunk2REOCMUDcjs.__decorateParam.call(void 0, 0, _common.Req.call(void 0, )),
532
+ _chunk2REOCMUDcjs.__decorateParam.call(void 0, 1, _common.Res.call(void 0, { passthrough: true }))
533
+ ], OAuthControllerHost.prototype, "refresh", 1);
534
+ _chunk2REOCMUDcjs.__decorateClass.call(void 0, [
535
+ _common.Post.call(void 0, "logout"),
536
+ _chunk2REOCMUDcjs.__decorateParam.call(void 0, 0, _common.Req.call(void 0, )),
537
+ _chunk2REOCMUDcjs.__decorateParam.call(void 0, 1, _common.Res.call(void 0, { passthrough: true }))
538
+ ], OAuthControllerHost.prototype, "logout", 1);
539
+ _chunk2REOCMUDcjs.__decorateClass.call(void 0, [
540
+ _common.UseGuards.call(void 0, Guard),
541
+ _common.Get.call(void 0, "me"),
542
+ _chunk2REOCMUDcjs.__decorateParam.call(void 0, 0, _chunkNVCI3CQIcjs.CurrentUser.call(void 0, ))
543
+ ], OAuthControllerHost.prototype, "me", 1);
544
+ _chunk2REOCMUDcjs.__decorateClass.call(void 0, [
545
+ _common.UseGuards.call(void 0, Guard),
546
+ _common.Get.call(void 0, "linked-accounts"),
547
+ _chunk2REOCMUDcjs.__decorateParam.call(void 0, 0, _chunkNVCI3CQIcjs.CurrentUser.call(void 0, ))
548
+ ], OAuthControllerHost.prototype, "linkedAccounts", 1);
549
+ _chunk2REOCMUDcjs.__decorateClass.call(void 0, [
550
+ _common.UseGuards.call(void 0, Guard),
551
+ _common.Post.call(void 0, "link-intent"),
552
+ _chunk2REOCMUDcjs.__decorateParam.call(void 0, 0, _chunkNVCI3CQIcjs.CurrentUser.call(void 0, )),
553
+ _chunk2REOCMUDcjs.__decorateParam.call(void 0, 1, _common.Res.call(void 0, { passthrough: true }))
554
+ ], OAuthControllerHost.prototype, "linkIntent", 1);
555
+ _chunk2REOCMUDcjs.__decorateClass.call(void 0, [
556
+ _common.Get.call(void 0, ":provider"),
557
+ _chunk2REOCMUDcjs.__decorateParam.call(void 0, 0, _common.Param.call(void 0, "provider")),
558
+ _chunk2REOCMUDcjs.__decorateParam.call(void 0, 1, _common.Res.call(void 0, ))
559
+ ], OAuthControllerHost.prototype, "start", 1);
560
+ _chunk2REOCMUDcjs.__decorateClass.call(void 0, [
561
+ _common.Get.call(void 0, ":provider/callback"),
562
+ _common.UseFilters.call(void 0, OAuthCallbackFilter),
563
+ _chunk2REOCMUDcjs.__decorateParam.call(void 0, 0, _common.Param.call(void 0, "provider")),
564
+ _chunk2REOCMUDcjs.__decorateParam.call(void 0, 1, _common.Query.call(void 0, "code")),
565
+ _chunk2REOCMUDcjs.__decorateParam.call(void 0, 2, _common.Req.call(void 0, )),
566
+ _chunk2REOCMUDcjs.__decorateParam.call(void 0, 3, _common.Res.call(void 0, ))
567
+ ], OAuthControllerHost.prototype, "callback", 1);
568
+ OAuthControllerHost = _chunk2REOCMUDcjs.__decorateClass.call(void 0, [
569
+ _common.Controller.call(void 0, _nullishCoalesce(options.path, () => ( "auth")))
570
+ ], OAuthControllerHost);
571
+ return OAuthControllerHost;
572
+ }
573
+
574
+ // src/auth/oauth/oauth-auth.module.ts
575
+
576
+ var OAuthAuthModule = class {
577
+ static forRoot(config) {
578
+ return {
579
+ module: OAuthAuthModule,
580
+ imports: _nullishCoalesce(config.imports, () => ( [])),
581
+ providers: [
582
+ { provide: OAUTH_AUTH_OPTIONS, useValue: config.options },
583
+ ...config.providers,
584
+ OAuthAuthService,
585
+ OAuthCallbackFilter
586
+ ],
587
+ // The options travel out too: a consumer's own controller needs the
588
+ // cookie name and the redirect builders, and re-deriving them there is
589
+ // how the two copies drift apart.
590
+ exports: [OAuthAuthService, OAuthCallbackFilter, OAUTH_AUTH_OPTIONS]
591
+ };
592
+ }
593
+ };
594
+ OAuthAuthModule = exports.OAuthAuthModule = _chunk2REOCMUDcjs.__decorateClass.call(void 0, [
595
+ _common.Module.call(void 0, {})
596
+ ], OAuthAuthModule);
597
+
598
+
599
+
600
+
601
+
602
+
603
+
604
+
605
+
606
+
607
+
608
+
609
+
610
+ exports.OAUTH_AUTH_OPTIONS = OAUTH_AUTH_OPTIONS; exports.OAUTH_STORE = OAUTH_STORE; exports.OAuthAuthModule = OAuthAuthModule; exports.OAuthAuthService = OAuthAuthService; exports.OAuthCallbackFilter = OAuthCallbackFilter; exports.REFRESH_TOKEN_STORE = REFRESH_TOKEN_STORE; exports.buildAuthorizeUrl = buildAuthorizeUrl; exports.createOAuthController = createOAuthController; exports.exchangeCodeForToken = exchangeCodeForToken; exports.facebookProvider = facebookProvider; exports.googleProvider = googleProvider; exports.resolveOAuthOptions = resolveOAuthOptions;
@@ -0,0 +1,12 @@
1
+ export { googleProvider, facebookProvider, buildAuthorizeUrl, exchangeCodeForToken, } from './oauth-provider.js';
2
+ export type { OAuthProfile, OAuthProviderConfig, ProviderCredentials, } from './oauth-provider.js';
3
+ export type { OAuthUser, OAuthAccount, OAuthStorePort, RefreshTokenStorePort, ProfileSyncPolicy, } from './oauth.ports.js';
4
+ export { OAUTH_AUTH_OPTIONS, OAUTH_STORE, REFRESH_TOKEN_STORE, resolveOAuthOptions, } from './oauth.options.js';
5
+ export type { OAuthAuthOptions, OAuthCookieOptions, ResolvedOAuthOptions, } from './oauth.options.js';
6
+ export { OAuthAuthService } from './oauth-auth.service.js';
7
+ export type { TokenPair } from './oauth-auth.service.js';
8
+ export { OAuthCallbackFilter } from './oauth-callback.filter.js';
9
+ export { createOAuthController } from './oauth.controller.js';
10
+ export type { CreateOAuthControllerOptions } from './oauth.controller.js';
11
+ export { OAuthAuthModule } from './oauth-auth.module.js';
12
+ export type { OAuthAuthModuleOptions } from './oauth-auth.module.js';