better-auth-cognito-native 0.1.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.
- package/dist/client.d.mts +148 -0
- package/dist/client.mjs +179 -0
- package/dist/config-C3_h3skc.d.mts +35 -0
- package/dist/config.d.mts +2 -0
- package/dist/config.mjs +39 -0
- package/dist/error-codes-O-Ljx2JX.mjs +30 -0
- package/dist/index-D4C-gqBy.d.mts +213 -0
- package/dist/index.d.mts +3 -0
- package/dist/index.mjs +679 -0
- package/package.json +59 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,679 @@
|
|
|
1
|
+
import { t as COGNITO_ERROR_CODES } from "./error-codes-O-Ljx2JX.mjs";
|
|
2
|
+
import { getPasskeyConfig } from "./config.mjs";
|
|
3
|
+
import { AliasExistsException, CodeMismatchException, CognitoIdentityProviderClient, CompleteWebAuthnRegistrationCommand, ConfirmForgotPasswordCommand, ConfirmSignUpCommand, ExpiredCodeException, ForgotPasswordCommand, GetUserCommand, GlobalSignOutCommand, InitiateAuthCommand, InvalidParameterException, InvalidPasswordException, LimitExceededException, NotAuthorizedException, ResendConfirmationCodeCommand, RespondToAuthChallengeCommand, SignUpCommand, StartWebAuthnRegistrationCommand, UpdateUserAttributesCommand, UserLambdaValidationException, UserNotConfirmedException, UserNotFoundException, UsernameExistsException, WebAuthnChallengeNotFoundException, WebAuthnConfigurationMissingException, WebAuthnNotEnabledException, WebAuthnOriginNotAllowedException, WebAuthnRelyingPartyMismatchException } from "@aws-sdk/client-cognito-identity-provider";
|
|
4
|
+
import { createRemoteJWKSet, jwtVerify } from "jose";
|
|
5
|
+
import { APIError, createAuthEndpoint, getSessionFromCtx } from "better-auth/api";
|
|
6
|
+
import { deleteSessionCookie, setSessionCookie } from "better-auth/cookies";
|
|
7
|
+
import { symmetricDecodeJWT, symmetricEncodeJWT } from "better-auth/crypto";
|
|
8
|
+
import * as z from "zod";
|
|
9
|
+
//#region src/routes.ts
|
|
10
|
+
const cognitoSignInBodySchema = z.object({
|
|
11
|
+
email: z.email(),
|
|
12
|
+
password: z.string().min(1),
|
|
13
|
+
rememberMe: z.boolean().optional()
|
|
14
|
+
});
|
|
15
|
+
const cognitoSignUpBodySchema = z.object({
|
|
16
|
+
email: z.email(),
|
|
17
|
+
password: z.string().min(1)
|
|
18
|
+
});
|
|
19
|
+
const cognitoConfirmSignUpBodySchema = z.object({
|
|
20
|
+
email: z.email(),
|
|
21
|
+
code: z.string().trim().min(1)
|
|
22
|
+
});
|
|
23
|
+
const cognitoResendConfirmationCodeBodySchema = z.object({ email: z.email() });
|
|
24
|
+
const cognitoForgotPasswordBodySchema = z.object({ email: z.email() });
|
|
25
|
+
const cognitoConfirmForgotPasswordBodySchema = z.object({
|
|
26
|
+
email: z.email(),
|
|
27
|
+
code: z.string().trim().min(1),
|
|
28
|
+
newPassword: z.string().min(1)
|
|
29
|
+
});
|
|
30
|
+
const cognitoNewPasswordBodySchema = z.object({
|
|
31
|
+
email: z.email(),
|
|
32
|
+
newPassword: z.string().min(1),
|
|
33
|
+
challengeSession: z.string().min(1),
|
|
34
|
+
rememberMe: z.boolean().optional()
|
|
35
|
+
});
|
|
36
|
+
const cognitoSignOutBodySchema = z.object({ redirectTo: z.string().optional() });
|
|
37
|
+
const cognitoStartPasskeySignInBodySchema = z.object({ email: z.email() });
|
|
38
|
+
const cognitoCompletePasskeySignInBodySchema = z.object({
|
|
39
|
+
email: z.email(),
|
|
40
|
+
credential: z.record(z.string(), z.unknown()),
|
|
41
|
+
session: z.string().min(1),
|
|
42
|
+
rememberMe: z.boolean().optional()
|
|
43
|
+
});
|
|
44
|
+
const cognitoCompletePasskeyRegistrationBodySchema = z.object({ credential: z.record(z.string(), z.unknown()) });
|
|
45
|
+
const cognitoUpdateUserAttributesBodySchema = z.object({ attributes: z.array(z.object({
|
|
46
|
+
Name: z.string().min(1),
|
|
47
|
+
Value: z.string()
|
|
48
|
+
})).min(1) });
|
|
49
|
+
const COGNITO_COOKIE = "cognito_tokens";
|
|
50
|
+
const COGNITO_COOKIE_DEFAULT_MAX_AGE = 3600;
|
|
51
|
+
const COGNITO_COOKIE_SUBJECT = "better-auth-cognito";
|
|
52
|
+
function isStatefulAuth(ctx) {
|
|
53
|
+
return !!ctx.context.options.database || !!ctx.context.options.secondaryStorage;
|
|
54
|
+
}
|
|
55
|
+
async function writeCognitoTokenCookie(ctx, payload) {
|
|
56
|
+
const cookie = ctx.context.createAuthCookie(COGNITO_COOKIE, { maxAge: COGNITO_COOKIE_DEFAULT_MAX_AGE });
|
|
57
|
+
const maxAge = typeof cookie.attributes.maxAge === "number" ? cookie.attributes.maxAge : COGNITO_COOKIE_DEFAULT_MAX_AGE;
|
|
58
|
+
const encoded = await symmetricEncodeJWT(payload, ctx.context.secretConfig, COGNITO_COOKIE_SUBJECT, maxAge);
|
|
59
|
+
ctx.setCookie(cookie.name, encoded, cookie.attributes);
|
|
60
|
+
}
|
|
61
|
+
async function readCognitoTokenCookie(ctx, userId) {
|
|
62
|
+
const cookie = ctx.context.createAuthCookie(COGNITO_COOKIE);
|
|
63
|
+
const raw = ctx.getCookie(cookie.name);
|
|
64
|
+
if (!raw) return null;
|
|
65
|
+
try {
|
|
66
|
+
const data = await symmetricDecodeJWT(raw, ctx.context.secretConfig, COGNITO_COOKIE_SUBJECT);
|
|
67
|
+
if (data?.userId === userId) return data;
|
|
68
|
+
} catch {}
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
function clearCognitoTokenCookie(ctx) {
|
|
72
|
+
const cognitoCookie = ctx.context.createAuthCookie(COGNITO_COOKIE);
|
|
73
|
+
ctx.setCookie(cognitoCookie.name, "", {
|
|
74
|
+
...cognitoCookie.attributes,
|
|
75
|
+
maxAge: 0
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
async function getCognitoTokens(ctx, userId) {
|
|
79
|
+
if (!isStatefulAuth(ctx)) {
|
|
80
|
+
const fromCookie = await readCognitoTokenCookie(ctx, userId);
|
|
81
|
+
if (fromCookie) return {
|
|
82
|
+
accessToken: fromCookie.accessToken,
|
|
83
|
+
idToken: fromCookie.idToken
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
const account = (await ctx.context.internalAdapter.findAccounts(userId).catch(() => [])).find((a) => a.providerId === "cognito");
|
|
87
|
+
if (account) return {
|
|
88
|
+
accessToken: account.accessToken ?? null,
|
|
89
|
+
idToken: account.idToken ?? null
|
|
90
|
+
};
|
|
91
|
+
if (isStatefulAuth(ctx)) {
|
|
92
|
+
const fromCookie = await readCognitoTokenCookie(ctx, userId);
|
|
93
|
+
if (fromCookie) return {
|
|
94
|
+
accessToken: fromCookie.accessToken,
|
|
95
|
+
idToken: fromCookie.idToken
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
return {
|
|
99
|
+
accessToken: null,
|
|
100
|
+
idToken: null
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
async function getCognitoAccessToken(ctx, userId) {
|
|
104
|
+
const { accessToken } = await getCognitoTokens(ctx, userId);
|
|
105
|
+
return accessToken;
|
|
106
|
+
}
|
|
107
|
+
async function upsertCognitoAccount(ctx, userId, accountId, tokenData) {
|
|
108
|
+
const accountFields = {
|
|
109
|
+
providerId: "cognito",
|
|
110
|
+
accountId,
|
|
111
|
+
userId,
|
|
112
|
+
accessToken: tokenData.accessToken,
|
|
113
|
+
refreshToken: tokenData.refreshToken ?? null,
|
|
114
|
+
idToken: tokenData.idToken,
|
|
115
|
+
accessTokenExpiresAt: tokenData.accessTokenExpiresAt,
|
|
116
|
+
scope: "openid profile email"
|
|
117
|
+
};
|
|
118
|
+
if (!isStatefulAuth(ctx)) {
|
|
119
|
+
await writeCognitoTokenCookie(ctx, {
|
|
120
|
+
userId,
|
|
121
|
+
accessToken: tokenData.accessToken ?? null,
|
|
122
|
+
idToken: tokenData.idToken
|
|
123
|
+
});
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
const existing = await ctx.context.internalAdapter.findAccounts(userId).then((accounts) => accounts.find((a) => a.providerId === "cognito")).catch(() => null);
|
|
127
|
+
if (existing) await ctx.context.internalAdapter.updateAccount(existing.id, accountFields);
|
|
128
|
+
else await ctx.context.internalAdapter.createAccount(accountFields);
|
|
129
|
+
}
|
|
130
|
+
function cognitoGetTokensEndpoint(pCtx) {
|
|
131
|
+
return createAuthEndpoint("/cognito/tokens", { method: "GET" }, async (ctx) => {
|
|
132
|
+
const activeSession = await getSessionFromCtx(ctx);
|
|
133
|
+
if (!activeSession) throw new APIError("UNAUTHORIZED");
|
|
134
|
+
const { accessToken, idToken } = await getCognitoTokens(ctx, activeSession.user.id);
|
|
135
|
+
return ctx.json({
|
|
136
|
+
accessToken,
|
|
137
|
+
idToken
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
function cognitoSignUpEndpoint(pCtx) {
|
|
142
|
+
return createAuthEndpoint("/cognito/sign-up", {
|
|
143
|
+
method: "POST",
|
|
144
|
+
body: cognitoSignUpBodySchema
|
|
145
|
+
}, async (ctx) => {
|
|
146
|
+
const { email, password } = ctx.body;
|
|
147
|
+
try {
|
|
148
|
+
const result = await pCtx.client.send(new SignUpCommand({
|
|
149
|
+
ClientId: pCtx.clientId,
|
|
150
|
+
Username: email,
|
|
151
|
+
Password: password,
|
|
152
|
+
UserAttributes: [{
|
|
153
|
+
Name: "email",
|
|
154
|
+
Value: email
|
|
155
|
+
}]
|
|
156
|
+
}));
|
|
157
|
+
return ctx.json({
|
|
158
|
+
userConfirmed: result.UserConfirmed,
|
|
159
|
+
userSub: result.UserSub,
|
|
160
|
+
codeDeliveryDetails: result.CodeDeliveryDetails ?? null
|
|
161
|
+
});
|
|
162
|
+
} catch (err) {
|
|
163
|
+
if (err instanceof UsernameExistsException) throw new APIError("CONFLICT", { message: COGNITO_ERROR_CODES.ACCOUNT_ALREADY_EXISTS });
|
|
164
|
+
if (err instanceof LimitExceededException) return ctx.json({
|
|
165
|
+
userConfirmed: false,
|
|
166
|
+
userSub: email,
|
|
167
|
+
codeDeliveryDetails: null
|
|
168
|
+
});
|
|
169
|
+
if (err instanceof InvalidPasswordException || err instanceof InvalidParameterException || err instanceof UserLambdaValidationException) throw new APIError("BAD_REQUEST", { message: err.message ?? COGNITO_ERROR_CODES.SIGN_UP_FAILED });
|
|
170
|
+
ctx.context.logger.error("cognito/sign-up: unexpected Cognito error", err);
|
|
171
|
+
throw new APIError("INTERNAL_SERVER_ERROR", { message: COGNITO_ERROR_CODES.SIGN_UP_FAILED });
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
function cognitoConfirmSignUpEndpoint(pCtx) {
|
|
176
|
+
return createAuthEndpoint("/cognito/confirm-sign-up", {
|
|
177
|
+
method: "POST",
|
|
178
|
+
body: cognitoConfirmSignUpBodySchema
|
|
179
|
+
}, async (ctx) => {
|
|
180
|
+
const { email, code } = ctx.body;
|
|
181
|
+
try {
|
|
182
|
+
await pCtx.client.send(new ConfirmSignUpCommand({
|
|
183
|
+
ClientId: pCtx.clientId,
|
|
184
|
+
Username: email,
|
|
185
|
+
ConfirmationCode: code
|
|
186
|
+
}));
|
|
187
|
+
return ctx.json({ confirmed: true });
|
|
188
|
+
} catch (err) {
|
|
189
|
+
if (err instanceof CodeMismatchException) throw new APIError("BAD_REQUEST", { message: COGNITO_ERROR_CODES.CONFIRMATION_CODE_MISMATCH });
|
|
190
|
+
if (err instanceof ExpiredCodeException) throw new APIError("BAD_REQUEST", { message: COGNITO_ERROR_CODES.CONFIRMATION_CODE_EXPIRED });
|
|
191
|
+
if (err instanceof UserNotFoundException) throw new APIError("NOT_FOUND", { message: COGNITO_ERROR_CODES.USER_NOT_FOUND });
|
|
192
|
+
if (err instanceof AliasExistsException || err instanceof InvalidParameterException || err instanceof NotAuthorizedException) throw new APIError("BAD_REQUEST", { message: err.message ?? COGNITO_ERROR_CODES.CONFIRMATION_FAILED });
|
|
193
|
+
ctx.context.logger.error("cognito/confirm-sign-up: unexpected Cognito error", err);
|
|
194
|
+
throw new APIError("INTERNAL_SERVER_ERROR", { message: COGNITO_ERROR_CODES.CONFIRMATION_FAILED });
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
function cognitoResendConfirmationCodeEndpoint(pCtx) {
|
|
199
|
+
return createAuthEndpoint("/cognito/resend-confirmation", {
|
|
200
|
+
method: "POST",
|
|
201
|
+
body: cognitoResendConfirmationCodeBodySchema
|
|
202
|
+
}, async (ctx) => {
|
|
203
|
+
const { email } = ctx.body;
|
|
204
|
+
try {
|
|
205
|
+
const result = await pCtx.client.send(new ResendConfirmationCodeCommand({
|
|
206
|
+
ClientId: pCtx.clientId,
|
|
207
|
+
Username: email
|
|
208
|
+
}));
|
|
209
|
+
return ctx.json({ codeDeliveryDetails: result.CodeDeliveryDetails ?? null });
|
|
210
|
+
} catch (err) {
|
|
211
|
+
if (err instanceof UserNotFoundException) throw new APIError("NOT_FOUND", { message: COGNITO_ERROR_CODES.USER_NOT_FOUND });
|
|
212
|
+
if (err instanceof InvalidParameterException || err instanceof LimitExceededException || err instanceof NotAuthorizedException) throw new APIError("BAD_REQUEST", { message: err.message ?? COGNITO_ERROR_CODES.RESEND_CONFIRMATION_FAILED });
|
|
213
|
+
ctx.context.logger.error("cognito/resend-confirmation: unexpected Cognito error", err);
|
|
214
|
+
throw new APIError("INTERNAL_SERVER_ERROR", { message: COGNITO_ERROR_CODES.RESEND_CONFIRMATION_FAILED });
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
function cognitoForgotPasswordEndpoint(pCtx) {
|
|
219
|
+
return createAuthEndpoint("/cognito/forgot-password", {
|
|
220
|
+
method: "POST",
|
|
221
|
+
body: cognitoForgotPasswordBodySchema
|
|
222
|
+
}, async (ctx) => {
|
|
223
|
+
const { email } = ctx.body;
|
|
224
|
+
try {
|
|
225
|
+
const result = await pCtx.client.send(new ForgotPasswordCommand({
|
|
226
|
+
ClientId: pCtx.clientId,
|
|
227
|
+
Username: email
|
|
228
|
+
}));
|
|
229
|
+
return ctx.json({ codeDeliveryDetails: result.CodeDeliveryDetails ?? null });
|
|
230
|
+
} catch (err) {
|
|
231
|
+
if (err instanceof UserNotFoundException) throw new APIError("NOT_FOUND", { message: COGNITO_ERROR_CODES.USER_NOT_FOUND });
|
|
232
|
+
if (err instanceof InvalidParameterException || err instanceof LimitExceededException || err instanceof NotAuthorizedException || err instanceof UserLambdaValidationException) throw new APIError("BAD_REQUEST", { message: err.message ?? COGNITO_ERROR_CODES.FORGOT_PASSWORD_FAILED });
|
|
233
|
+
ctx.context.logger.error("cognito/forgot-password: unexpected Cognito error", err);
|
|
234
|
+
throw new APIError("INTERNAL_SERVER_ERROR", { message: COGNITO_ERROR_CODES.FORGOT_PASSWORD_FAILED });
|
|
235
|
+
}
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
function cognitoConfirmForgotPasswordEndpoint(pCtx) {
|
|
239
|
+
return createAuthEndpoint("/cognito/confirm-forgot-password", {
|
|
240
|
+
method: "POST",
|
|
241
|
+
body: cognitoConfirmForgotPasswordBodySchema
|
|
242
|
+
}, async (ctx) => {
|
|
243
|
+
const { email, code, newPassword } = ctx.body;
|
|
244
|
+
try {
|
|
245
|
+
await pCtx.client.send(new ConfirmForgotPasswordCommand({
|
|
246
|
+
ClientId: pCtx.clientId,
|
|
247
|
+
Username: email,
|
|
248
|
+
ConfirmationCode: code,
|
|
249
|
+
Password: newPassword
|
|
250
|
+
}));
|
|
251
|
+
return ctx.json({ reset: true });
|
|
252
|
+
} catch (err) {
|
|
253
|
+
if (err instanceof CodeMismatchException) throw new APIError("BAD_REQUEST", { message: COGNITO_ERROR_CODES.CONFIRMATION_CODE_MISMATCH });
|
|
254
|
+
if (err instanceof ExpiredCodeException) throw new APIError("BAD_REQUEST", { message: COGNITO_ERROR_CODES.CONFIRMATION_CODE_EXPIRED });
|
|
255
|
+
if (err instanceof UserNotFoundException) throw new APIError("NOT_FOUND", { message: COGNITO_ERROR_CODES.USER_NOT_FOUND });
|
|
256
|
+
if (err instanceof InvalidPasswordException || err instanceof InvalidParameterException || err instanceof LimitExceededException || err instanceof NotAuthorizedException || err instanceof UserLambdaValidationException) throw new APIError("BAD_REQUEST", { message: err.message ?? COGNITO_ERROR_CODES.FORGOT_PASSWORD_CONFIRM_FAILED });
|
|
257
|
+
ctx.context.logger.error("cognito/confirm-forgot-password: unexpected Cognito error", err);
|
|
258
|
+
throw new APIError("INTERNAL_SERVER_ERROR", { message: COGNITO_ERROR_CODES.FORGOT_PASSWORD_CONFIRM_FAILED });
|
|
259
|
+
}
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
function cognitoSignInEndpoint(pCtx) {
|
|
263
|
+
return createAuthEndpoint("/cognito/sign-in", {
|
|
264
|
+
method: "POST",
|
|
265
|
+
body: cognitoSignInBodySchema
|
|
266
|
+
}, async (ctx) => {
|
|
267
|
+
const { email, password, rememberMe } = ctx.body;
|
|
268
|
+
let authResult;
|
|
269
|
+
try {
|
|
270
|
+
authResult = await pCtx.client.send(new InitiateAuthCommand({
|
|
271
|
+
AuthFlow: "USER_PASSWORD_AUTH",
|
|
272
|
+
ClientId: pCtx.clientId,
|
|
273
|
+
AuthParameters: {
|
|
274
|
+
USERNAME: email,
|
|
275
|
+
PASSWORD: password
|
|
276
|
+
}
|
|
277
|
+
}));
|
|
278
|
+
} catch (err) {
|
|
279
|
+
if (err instanceof NotAuthorizedException || err instanceof UserNotFoundException) throw new APIError("UNAUTHORIZED", { message: COGNITO_ERROR_CODES.INVALID_CREDENTIALS });
|
|
280
|
+
if (err instanceof UserNotConfirmedException) throw new APIError("FORBIDDEN", { message: COGNITO_ERROR_CODES.USER_NOT_CONFIRMED });
|
|
281
|
+
ctx.context.logger.error("cognito/sign-in: unexpected Cognito auth error", err);
|
|
282
|
+
throw new APIError("INTERNAL_SERVER_ERROR", { message: COGNITO_ERROR_CODES.AUTH_SERVICE_ERROR });
|
|
283
|
+
}
|
|
284
|
+
if (authResult.ChallengeName === "NEW_PASSWORD_REQUIRED") return ctx.json({
|
|
285
|
+
type: "NEW_PASSWORD_REQUIRED",
|
|
286
|
+
challengeSession: authResult.Session
|
|
287
|
+
});
|
|
288
|
+
const tokens = authResult.AuthenticationResult;
|
|
289
|
+
if (!tokens?.IdToken) {
|
|
290
|
+
ctx.context.logger.error("cognito/sign-in: no IdToken in Cognito response");
|
|
291
|
+
throw new APIError("INTERNAL_SERVER_ERROR", { message: COGNITO_ERROR_CODES.NO_TOKEN_FROM_COGNITO });
|
|
292
|
+
}
|
|
293
|
+
let claims;
|
|
294
|
+
try {
|
|
295
|
+
claims = await pCtx.verifyToken(tokens.IdToken);
|
|
296
|
+
} catch (err) {
|
|
297
|
+
ctx.context.logger.error("cognito/sign-in: token verification failed", err);
|
|
298
|
+
throw new APIError("INTERNAL_SERVER_ERROR", { message: COGNITO_ERROR_CODES.TOKEN_VERIFICATION_FAILED });
|
|
299
|
+
}
|
|
300
|
+
const existing = await ctx.context.internalAdapter.findUserByEmail(email, { includeAccounts: false }).catch(() => null);
|
|
301
|
+
const user = existing ? await ctx.context.internalAdapter.updateUser(existing.user.id, {
|
|
302
|
+
name: claims.name,
|
|
303
|
+
emailVerified: true
|
|
304
|
+
}) : await ctx.context.internalAdapter.createUser({
|
|
305
|
+
email,
|
|
306
|
+
name: claims.name,
|
|
307
|
+
emailVerified: true,
|
|
308
|
+
id: claims.sub
|
|
309
|
+
});
|
|
310
|
+
await setSessionCookie(ctx, {
|
|
311
|
+
session: await ctx.context.internalAdapter.createSession(user.id, rememberMe === false),
|
|
312
|
+
user
|
|
313
|
+
}, rememberMe === false);
|
|
314
|
+
await upsertCognitoAccount(ctx, user.id, claims.sub, {
|
|
315
|
+
accessToken: tokens.AccessToken,
|
|
316
|
+
refreshToken: tokens.RefreshToken,
|
|
317
|
+
idToken: tokens.IdToken,
|
|
318
|
+
accessTokenExpiresAt: new Date(Date.now() + 3600 * 1e3)
|
|
319
|
+
});
|
|
320
|
+
return ctx.json({ ok: true });
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
function cognitoNewPasswordEndpoint(pCtx) {
|
|
324
|
+
return createAuthEndpoint("/cognito/new-password", {
|
|
325
|
+
method: "POST",
|
|
326
|
+
body: cognitoNewPasswordBodySchema
|
|
327
|
+
}, async (ctx) => {
|
|
328
|
+
const { email, newPassword, challengeSession, rememberMe } = ctx.body;
|
|
329
|
+
let tokens;
|
|
330
|
+
try {
|
|
331
|
+
tokens = (await pCtx.client.send(new RespondToAuthChallengeCommand({
|
|
332
|
+
ClientId: pCtx.clientId,
|
|
333
|
+
ChallengeName: "NEW_PASSWORD_REQUIRED",
|
|
334
|
+
Session: challengeSession,
|
|
335
|
+
ChallengeResponses: {
|
|
336
|
+
USERNAME: email,
|
|
337
|
+
NEW_PASSWORD: newPassword
|
|
338
|
+
}
|
|
339
|
+
}))).AuthenticationResult;
|
|
340
|
+
} catch (err) {
|
|
341
|
+
throw new APIError("BAD_REQUEST", { message: err instanceof Error ? err.message : "Failed to set new password" });
|
|
342
|
+
}
|
|
343
|
+
if (!tokens?.IdToken) {
|
|
344
|
+
ctx.context.logger.error("cognito/new-password: no IdToken in Cognito response");
|
|
345
|
+
throw new APIError("INTERNAL_SERVER_ERROR", { message: COGNITO_ERROR_CODES.NO_TOKEN_FROM_COGNITO });
|
|
346
|
+
}
|
|
347
|
+
let claims;
|
|
348
|
+
try {
|
|
349
|
+
claims = await pCtx.verifyToken(tokens.IdToken);
|
|
350
|
+
} catch (err) {
|
|
351
|
+
ctx.context.logger.error("cognito/new-password: token verification failed", err);
|
|
352
|
+
throw new APIError("INTERNAL_SERVER_ERROR", { message: COGNITO_ERROR_CODES.TOKEN_VERIFICATION_FAILED });
|
|
353
|
+
}
|
|
354
|
+
const existing = await ctx.context.internalAdapter.findUserByEmail(email, { includeAccounts: false }).catch(() => null);
|
|
355
|
+
const user = existing ? await ctx.context.internalAdapter.updateUser(existing.user.id, {
|
|
356
|
+
name: claims.name,
|
|
357
|
+
emailVerified: true
|
|
358
|
+
}) : await ctx.context.internalAdapter.createUser({
|
|
359
|
+
email,
|
|
360
|
+
name: claims.name,
|
|
361
|
+
emailVerified: true,
|
|
362
|
+
id: claims.sub
|
|
363
|
+
});
|
|
364
|
+
await setSessionCookie(ctx, {
|
|
365
|
+
session: await ctx.context.internalAdapter.createSession(user.id, rememberMe === false),
|
|
366
|
+
user
|
|
367
|
+
}, rememberMe === false);
|
|
368
|
+
await upsertCognitoAccount(ctx, user.id, claims.sub, {
|
|
369
|
+
accessToken: tokens.AccessToken,
|
|
370
|
+
refreshToken: tokens.RefreshToken,
|
|
371
|
+
idToken: tokens.IdToken,
|
|
372
|
+
accessTokenExpiresAt: new Date(Date.now() + 3600 * 1e3)
|
|
373
|
+
});
|
|
374
|
+
return ctx.json({ ok: true });
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
function cognitoStartPasskeySignInEndpoint(pCtx) {
|
|
378
|
+
return createAuthEndpoint("/cognito/passkey/sign-in/start", {
|
|
379
|
+
method: "POST",
|
|
380
|
+
body: cognitoStartPasskeySignInBodySchema
|
|
381
|
+
}, async (ctx) => {
|
|
382
|
+
const { email } = ctx.body;
|
|
383
|
+
let result;
|
|
384
|
+
try {
|
|
385
|
+
result = await pCtx.client.send(new InitiateAuthCommand({
|
|
386
|
+
AuthFlow: "USER_AUTH",
|
|
387
|
+
ClientId: pCtx.clientId,
|
|
388
|
+
AuthParameters: {
|
|
389
|
+
USERNAME: email,
|
|
390
|
+
PREFERRED_CHALLENGE: "WEB_AUTHN"
|
|
391
|
+
}
|
|
392
|
+
}));
|
|
393
|
+
} catch (err) {
|
|
394
|
+
if (err instanceof UserNotFoundException || err instanceof NotAuthorizedException) throw new APIError("UNAUTHORIZED", { message: COGNITO_ERROR_CODES.USER_NOT_FOUND });
|
|
395
|
+
if (err instanceof WebAuthnNotEnabledException || err instanceof WebAuthnConfigurationMissingException) throw new APIError("BAD_REQUEST", { message: COGNITO_ERROR_CODES.PASSKEYS_NOT_ENABLED });
|
|
396
|
+
ctx.context.logger.error("cognito/passkey/sign-in/start: unexpected Cognito error", err);
|
|
397
|
+
throw new APIError("INTERNAL_SERVER_ERROR", { message: COGNITO_ERROR_CODES.AUTH_SERVICE_ERROR });
|
|
398
|
+
}
|
|
399
|
+
if (result.ChallengeName === "SELECT_CHALLENGE") {
|
|
400
|
+
if (!(result.AvailableChallenges ?? []).includes("WEB_AUTHN")) throw new APIError("BAD_REQUEST", { message: COGNITO_ERROR_CODES.NO_PASSKEYS_REGISTERED });
|
|
401
|
+
try {
|
|
402
|
+
result = await pCtx.client.send(new RespondToAuthChallengeCommand({
|
|
403
|
+
ClientId: pCtx.clientId,
|
|
404
|
+
ChallengeName: "SELECT_CHALLENGE",
|
|
405
|
+
Session: result.Session,
|
|
406
|
+
ChallengeResponses: {
|
|
407
|
+
USERNAME: email,
|
|
408
|
+
ANSWER: "WEB_AUTHN"
|
|
409
|
+
}
|
|
410
|
+
}));
|
|
411
|
+
} catch (err) {
|
|
412
|
+
ctx.context.logger.error("cognito/passkey/sign-in/start: SELECT_CHALLENGE respond failed", err);
|
|
413
|
+
throw new APIError("INTERNAL_SERVER_ERROR", { message: COGNITO_ERROR_CODES.AUTH_SERVICE_ERROR });
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
if (result.ChallengeName !== "WEB_AUTHN") throw new APIError("BAD_REQUEST", { message: `Passkey sign-in is not available (challenge: ${result.ChallengeName ?? "none"})` });
|
|
417
|
+
const rawOptions = result.ChallengeParameters?.CREDENTIAL_REQUEST_OPTIONS ?? result.ChallengeParameters?.PASSKEY_REQUEST_OPTIONS ?? Object.values(result.ChallengeParameters ?? {}).find((v) => typeof v === "string" && v.trimStart().startsWith("{"));
|
|
418
|
+
if (!rawOptions) {
|
|
419
|
+
ctx.context.logger.error(`cognito/passkey/sign-in/start: no credential request options. Keys: ${Object.keys(result.ChallengeParameters ?? {}).join(", ")}`);
|
|
420
|
+
throw new APIError("INTERNAL_SERVER_ERROR", { message: `${COGNITO_ERROR_CODES.PASSKEY_NO_OPTIONS_RETURNED}. Keys: ${Object.keys(result.ChallengeParameters ?? {}).join(", ")}` });
|
|
421
|
+
}
|
|
422
|
+
return ctx.json({
|
|
423
|
+
session: result.Session,
|
|
424
|
+
requestOptions: JSON.parse(rawOptions)
|
|
425
|
+
});
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
function cognitoCompletePasskeySignInEndpoint(pCtx) {
|
|
429
|
+
return createAuthEndpoint("/cognito/passkey/sign-in/complete", {
|
|
430
|
+
method: "POST",
|
|
431
|
+
body: cognitoCompletePasskeySignInBodySchema
|
|
432
|
+
}, async (ctx) => {
|
|
433
|
+
const { email, credential, session, rememberMe } = ctx.body;
|
|
434
|
+
let result;
|
|
435
|
+
try {
|
|
436
|
+
result = await pCtx.client.send(new RespondToAuthChallengeCommand({
|
|
437
|
+
ClientId: pCtx.clientId,
|
|
438
|
+
ChallengeName: "WEB_AUTHN",
|
|
439
|
+
Session: session,
|
|
440
|
+
ChallengeResponses: {
|
|
441
|
+
USERNAME: email,
|
|
442
|
+
CREDENTIAL: JSON.stringify(credential)
|
|
443
|
+
}
|
|
444
|
+
}));
|
|
445
|
+
} catch (err) {
|
|
446
|
+
if (err instanceof NotAuthorizedException || err instanceof WebAuthnChallengeNotFoundException || err instanceof WebAuthnRelyingPartyMismatchException) throw new APIError("UNAUTHORIZED", { message: COGNITO_ERROR_CODES.PASSKEY_VERIFICATION_FAILED });
|
|
447
|
+
ctx.context.logger.error("cognito/passkey/sign-in/complete: unexpected Cognito error", err);
|
|
448
|
+
throw new APIError("INTERNAL_SERVER_ERROR", { message: COGNITO_ERROR_CODES.AUTH_SERVICE_ERROR });
|
|
449
|
+
}
|
|
450
|
+
const tokens = result.AuthenticationResult;
|
|
451
|
+
if (!tokens?.IdToken) {
|
|
452
|
+
ctx.context.logger.error("cognito/passkey/sign-in/complete: no IdToken in Cognito response");
|
|
453
|
+
throw new APIError("INTERNAL_SERVER_ERROR", { message: COGNITO_ERROR_CODES.NO_TOKEN_FROM_COGNITO });
|
|
454
|
+
}
|
|
455
|
+
let claims;
|
|
456
|
+
try {
|
|
457
|
+
claims = await pCtx.verifyToken(tokens.IdToken);
|
|
458
|
+
} catch (err) {
|
|
459
|
+
ctx.context.logger.error("cognito/passkey/sign-in/complete: token verification failed", err);
|
|
460
|
+
throw new APIError("INTERNAL_SERVER_ERROR", { message: COGNITO_ERROR_CODES.TOKEN_VERIFICATION_FAILED });
|
|
461
|
+
}
|
|
462
|
+
const existing = await ctx.context.internalAdapter.findUserByEmail(email, { includeAccounts: false }).catch(() => null);
|
|
463
|
+
const user = existing ? await ctx.context.internalAdapter.updateUser(existing.user.id, {
|
|
464
|
+
name: claims.name,
|
|
465
|
+
emailVerified: true
|
|
466
|
+
}) : await ctx.context.internalAdapter.createUser({
|
|
467
|
+
email,
|
|
468
|
+
name: claims.name,
|
|
469
|
+
emailVerified: true,
|
|
470
|
+
id: claims.sub
|
|
471
|
+
});
|
|
472
|
+
const sessionRecord = await ctx.context.internalAdapter.createSession(user.id, rememberMe === false);
|
|
473
|
+
await setSessionCookie(ctx, {
|
|
474
|
+
session: sessionRecord,
|
|
475
|
+
user
|
|
476
|
+
}, rememberMe === false);
|
|
477
|
+
await upsertCognitoAccount(ctx, user.id, claims.sub, {
|
|
478
|
+
accessToken: tokens.AccessToken,
|
|
479
|
+
refreshToken: tokens.RefreshToken,
|
|
480
|
+
idToken: tokens.IdToken,
|
|
481
|
+
accessTokenExpiresAt: new Date(Date.now() + 3600 * 1e3)
|
|
482
|
+
});
|
|
483
|
+
return ctx.json({
|
|
484
|
+
redirect: false,
|
|
485
|
+
token: sessionRecord.token,
|
|
486
|
+
user
|
|
487
|
+
});
|
|
488
|
+
});
|
|
489
|
+
}
|
|
490
|
+
function cognitoStartPasskeyRegistrationEndpoint(pCtx) {
|
|
491
|
+
return createAuthEndpoint("/cognito/passkey/register/start", {
|
|
492
|
+
method: "POST",
|
|
493
|
+
body: z.object({})
|
|
494
|
+
}, async (ctx) => {
|
|
495
|
+
if (!await ctx.getSignedCookie(ctx.context.authCookies.sessionToken.name, ctx.context.secret)) throw new APIError("UNAUTHORIZED");
|
|
496
|
+
const activeSession = await getSessionFromCtx(ctx);
|
|
497
|
+
if (!activeSession) throw new APIError("UNAUTHORIZED");
|
|
498
|
+
const accessToken = await getCognitoAccessToken(ctx, activeSession.user.id);
|
|
499
|
+
if (!accessToken) throw new APIError("UNAUTHORIZED", { message: COGNITO_ERROR_CODES.NO_COGNITO_ACCESS_TOKEN });
|
|
500
|
+
let result;
|
|
501
|
+
try {
|
|
502
|
+
result = await pCtx.client.send(new StartWebAuthnRegistrationCommand({ AccessToken: accessToken }));
|
|
503
|
+
} catch (err) {
|
|
504
|
+
if (err instanceof WebAuthnNotEnabledException || err instanceof WebAuthnConfigurationMissingException) throw new APIError("BAD_REQUEST", { message: COGNITO_ERROR_CODES.PASSKEYS_NOT_ENABLED });
|
|
505
|
+
ctx.context.logger.error("cognito/passkey/register/start: unexpected Cognito error", err);
|
|
506
|
+
throw new APIError("INTERNAL_SERVER_ERROR", { message: COGNITO_ERROR_CODES.PASSKEY_REGISTRATION_START_FAILED });
|
|
507
|
+
}
|
|
508
|
+
return ctx.json({ credentialCreationOptions: result.CredentialCreationOptions });
|
|
509
|
+
});
|
|
510
|
+
}
|
|
511
|
+
function cognitoCompletePasskeyRegistrationEndpoint(pCtx) {
|
|
512
|
+
return createAuthEndpoint("/cognito/passkey/register/complete", {
|
|
513
|
+
method: "POST",
|
|
514
|
+
body: cognitoCompletePasskeyRegistrationBodySchema
|
|
515
|
+
}, async (ctx) => {
|
|
516
|
+
const { credential } = ctx.body;
|
|
517
|
+
if (!await ctx.getSignedCookie(ctx.context.authCookies.sessionToken.name, ctx.context.secret)) throw new APIError("UNAUTHORIZED");
|
|
518
|
+
const activeSession = await getSessionFromCtx(ctx);
|
|
519
|
+
if (!activeSession) throw new APIError("UNAUTHORIZED");
|
|
520
|
+
const accessToken = await getCognitoAccessToken(ctx, activeSession.user.id);
|
|
521
|
+
if (!accessToken) throw new APIError("UNAUTHORIZED", { message: COGNITO_ERROR_CODES.NO_COGNITO_ACCESS_TOKEN });
|
|
522
|
+
try {
|
|
523
|
+
await pCtx.client.send(new CompleteWebAuthnRegistrationCommand({
|
|
524
|
+
AccessToken: accessToken,
|
|
525
|
+
Credential: credential
|
|
526
|
+
}));
|
|
527
|
+
} catch (err) {
|
|
528
|
+
if (err instanceof WebAuthnOriginNotAllowedException) throw new APIError("BAD_REQUEST", { message: COGNITO_ERROR_CODES.PASSKEY_ORIGIN_NOT_ALLOWED });
|
|
529
|
+
if (err instanceof WebAuthnRelyingPartyMismatchException) throw new APIError("BAD_REQUEST", { message: COGNITO_ERROR_CODES.RELYING_PARTY_MISMATCH });
|
|
530
|
+
ctx.context.logger.error("cognito/passkey/register/complete: unexpected Cognito error", err);
|
|
531
|
+
throw new APIError("INTERNAL_SERVER_ERROR", { message: COGNITO_ERROR_CODES.PASSKEY_REGISTRATION_FAILED });
|
|
532
|
+
}
|
|
533
|
+
return ctx.json({ ok: true });
|
|
534
|
+
});
|
|
535
|
+
}
|
|
536
|
+
function cognitoGetUserAttributesEndpoint(pCtx) {
|
|
537
|
+
return createAuthEndpoint("/cognito/user-attributes", { method: "GET" }, async (ctx) => {
|
|
538
|
+
const activeSession = await getSessionFromCtx(ctx);
|
|
539
|
+
if (!activeSession) throw new APIError("UNAUTHORIZED");
|
|
540
|
+
const accessToken = await getCognitoAccessToken(ctx, activeSession.user.id);
|
|
541
|
+
if (!accessToken) throw new APIError("UNAUTHORIZED", { message: COGNITO_ERROR_CODES.NO_COGNITO_ACCESS_TOKEN });
|
|
542
|
+
try {
|
|
543
|
+
const result = await pCtx.client.send(new GetUserCommand({ AccessToken: accessToken }));
|
|
544
|
+
return ctx.json({ attributes: result.UserAttributes ?? [] });
|
|
545
|
+
} catch (err) {
|
|
546
|
+
if (err instanceof NotAuthorizedException) throw new APIError("UNAUTHORIZED", { message: COGNITO_ERROR_CODES.NO_COGNITO_ACCESS_TOKEN });
|
|
547
|
+
ctx.context.logger.error("cognito/user-attributes GET: unexpected Cognito error", err);
|
|
548
|
+
throw new APIError("INTERNAL_SERVER_ERROR", { message: COGNITO_ERROR_CODES.GET_USER_ATTRIBUTES_FAILED });
|
|
549
|
+
}
|
|
550
|
+
});
|
|
551
|
+
}
|
|
552
|
+
function cognitoUpdateUserAttributesEndpoint(pCtx) {
|
|
553
|
+
return createAuthEndpoint("/cognito/user-attributes", {
|
|
554
|
+
method: "POST",
|
|
555
|
+
body: cognitoUpdateUserAttributesBodySchema
|
|
556
|
+
}, async (ctx) => {
|
|
557
|
+
const activeSession = await getSessionFromCtx(ctx);
|
|
558
|
+
if (!activeSession) throw new APIError("UNAUTHORIZED");
|
|
559
|
+
const accessToken = await getCognitoAccessToken(ctx, activeSession.user.id);
|
|
560
|
+
if (!accessToken) throw new APIError("UNAUTHORIZED", { message: COGNITO_ERROR_CODES.NO_COGNITO_ACCESS_TOKEN });
|
|
561
|
+
try {
|
|
562
|
+
await pCtx.client.send(new UpdateUserAttributesCommand({
|
|
563
|
+
AccessToken: accessToken,
|
|
564
|
+
UserAttributes: ctx.body.attributes
|
|
565
|
+
}));
|
|
566
|
+
const nameAttr = ctx.body.attributes.find((a) => a.Name === "name");
|
|
567
|
+
if (nameAttr && isStatefulAuth(ctx)) await ctx.context.internalAdapter.updateUser(activeSession.user.id, { name: nameAttr.Value }).catch((err) => {
|
|
568
|
+
ctx.context.logger.error("cognito/user-attributes POST: failed to sync name to DB", err);
|
|
569
|
+
});
|
|
570
|
+
return ctx.json({ updated: true });
|
|
571
|
+
} catch (err) {
|
|
572
|
+
if (err instanceof NotAuthorizedException) throw new APIError("UNAUTHORIZED", { message: COGNITO_ERROR_CODES.NO_COGNITO_ACCESS_TOKEN });
|
|
573
|
+
if (err instanceof InvalidParameterException || err instanceof AliasExistsException) throw new APIError("BAD_REQUEST", { message: err.message ?? COGNITO_ERROR_CODES.UPDATE_USER_ATTRIBUTES_FAILED });
|
|
574
|
+
ctx.context.logger.error("cognito/user-attributes POST: unexpected Cognito error", err);
|
|
575
|
+
throw new APIError("INTERNAL_SERVER_ERROR", { message: COGNITO_ERROR_CODES.UPDATE_USER_ATTRIBUTES_FAILED });
|
|
576
|
+
}
|
|
577
|
+
});
|
|
578
|
+
}
|
|
579
|
+
function cognitoSignOutEndpoint(pCtx) {
|
|
580
|
+
return createAuthEndpoint("/cognito/sign-out", {
|
|
581
|
+
method: "POST",
|
|
582
|
+
body: cognitoSignOutBodySchema.optional()
|
|
583
|
+
}, async (ctx) => {
|
|
584
|
+
const sessionCookieToken = await ctx.getSignedCookie(ctx.context.authCookies.sessionToken.name, ctx.context.secret);
|
|
585
|
+
if (!sessionCookieToken) {
|
|
586
|
+
deleteSessionCookie(ctx);
|
|
587
|
+
return ctx.json({ success: true });
|
|
588
|
+
}
|
|
589
|
+
const activeSession = await getSessionFromCtx(ctx);
|
|
590
|
+
const accessToken = activeSession ? await getCognitoAccessToken(ctx, activeSession.user.id) : null;
|
|
591
|
+
if (accessToken) try {
|
|
592
|
+
await pCtx.client.send(new GlobalSignOutCommand({ AccessToken: accessToken }));
|
|
593
|
+
} catch (error) {
|
|
594
|
+
ctx.context.logger.error("Failed to sign out from Cognito", error);
|
|
595
|
+
}
|
|
596
|
+
try {
|
|
597
|
+
await ctx.context.internalAdapter.deleteSession(sessionCookieToken);
|
|
598
|
+
} catch (error) {
|
|
599
|
+
ctx.context.logger.error("Failed to delete session from database", error);
|
|
600
|
+
}
|
|
601
|
+
deleteSessionCookie(ctx);
|
|
602
|
+
clearCognitoTokenCookie(ctx);
|
|
603
|
+
return ctx.json({ success: true });
|
|
604
|
+
});
|
|
605
|
+
}
|
|
606
|
+
//#endregion
|
|
607
|
+
//#region src/index.ts
|
|
608
|
+
/**
|
|
609
|
+
* Better Auth server plugin for AWS Cognito.
|
|
610
|
+
*
|
|
611
|
+
* Registers endpoints:
|
|
612
|
+
* GET /cognito/tokens
|
|
613
|
+
* POST /cognito/sign-up
|
|
614
|
+
* POST /cognito/confirm-sign-up
|
|
615
|
+
* POST /cognito/resend-confirmation
|
|
616
|
+
* POST /cognito/forgot-password
|
|
617
|
+
* POST /cognito/confirm-forgot-password
|
|
618
|
+
* POST /cognito/sign-in
|
|
619
|
+
* POST /cognito/new-password
|
|
620
|
+
* POST /cognito/sign-out
|
|
621
|
+
* POST /cognito/passkey/sign-in/start
|
|
622
|
+
* POST /cognito/passkey/sign-in/complete
|
|
623
|
+
* POST /cognito/passkey/register/start
|
|
624
|
+
* POST /cognito/passkey/register/complete
|
|
625
|
+
*/
|
|
626
|
+
const cognitoPlugin = (opts) => {
|
|
627
|
+
const client = new CognitoIdentityProviderClient({ region: opts.region });
|
|
628
|
+
const jwks = createRemoteJWKSet(new URL(`https://cognito-idp.${opts.region}.amazonaws.com/${opts.userPoolId}/.well-known/jwks.json`));
|
|
629
|
+
const issuer = `https://cognito-idp.${opts.region}.amazonaws.com/${opts.userPoolId}`;
|
|
630
|
+
const verifyToken = async (idToken) => {
|
|
631
|
+
const { payload } = await jwtVerify(idToken, jwks, {
|
|
632
|
+
issuer,
|
|
633
|
+
audience: opts.clientId
|
|
634
|
+
});
|
|
635
|
+
const profile = payload;
|
|
636
|
+
return {
|
|
637
|
+
...profile,
|
|
638
|
+
name: `${profile.given_name ?? ""} ${profile.family_name ?? ""}`.trim() || profile.name || profile.email || "",
|
|
639
|
+
"cognito:groups": payload["cognito:groups"] ?? [],
|
|
640
|
+
idToken
|
|
641
|
+
};
|
|
642
|
+
};
|
|
643
|
+
const pCtx = {
|
|
644
|
+
client,
|
|
645
|
+
clientId: opts.clientId,
|
|
646
|
+
verifyToken
|
|
647
|
+
};
|
|
648
|
+
return {
|
|
649
|
+
id: "cognito",
|
|
650
|
+
endpoints: {
|
|
651
|
+
cognitoGetTokens: cognitoGetTokensEndpoint(pCtx),
|
|
652
|
+
cognitoGetUserAttributes: cognitoGetUserAttributesEndpoint(pCtx),
|
|
653
|
+
cognitoUpdateUserAttributes: cognitoUpdateUserAttributesEndpoint(pCtx),
|
|
654
|
+
cognitoSignUp: cognitoSignUpEndpoint(pCtx),
|
|
655
|
+
cognitoConfirmSignUp: cognitoConfirmSignUpEndpoint(pCtx),
|
|
656
|
+
cognitoResendConfirmationCode: cognitoResendConfirmationCodeEndpoint(pCtx),
|
|
657
|
+
cognitoForgotPassword: cognitoForgotPasswordEndpoint(pCtx),
|
|
658
|
+
cognitoConfirmForgotPassword: cognitoConfirmForgotPasswordEndpoint(pCtx),
|
|
659
|
+
cognitoSignIn: cognitoSignInEndpoint(pCtx),
|
|
660
|
+
cognitoNewPassword: cognitoNewPasswordEndpoint(pCtx),
|
|
661
|
+
cognitoStartPasskeySignIn: cognitoStartPasskeySignInEndpoint(pCtx),
|
|
662
|
+
cognitoCompletePasskeySignIn: cognitoCompletePasskeySignInEndpoint(pCtx),
|
|
663
|
+
cognitoStartPasskeyRegistration: cognitoStartPasskeyRegistrationEndpoint(pCtx),
|
|
664
|
+
cognitoCompletePasskeyRegistration: cognitoCompletePasskeyRegistrationEndpoint(pCtx),
|
|
665
|
+
cognitoSignOut: cognitoSignOutEndpoint(pCtx)
|
|
666
|
+
},
|
|
667
|
+
$ERROR_CODES: COGNITO_ERROR_CODES,
|
|
668
|
+
options: opts
|
|
669
|
+
};
|
|
670
|
+
};
|
|
671
|
+
/** Globally sign out from Cognito (invalidates the refresh token). Best-effort. */
|
|
672
|
+
async function cognitoGlobalSignOut(accessToken, opts) {
|
|
673
|
+
const client = new CognitoIdentityProviderClient({ region: opts.region });
|
|
674
|
+
try {
|
|
675
|
+
await client.send(new GlobalSignOutCommand({ AccessToken: accessToken }));
|
|
676
|
+
} catch {}
|
|
677
|
+
}
|
|
678
|
+
//#endregion
|
|
679
|
+
export { COGNITO_ERROR_CODES, cognitoGlobalSignOut, cognitoPlugin, getPasskeyConfig };
|