better-auth 1.6.10 → 1.6.12
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/api/index.d.mts +8 -2
- package/dist/api/routes/callback.d.mts +1 -1
- package/dist/api/routes/callback.mjs +36 -40
- package/dist/api/routes/email-verification.d.mts +1 -0
- package/dist/api/routes/email-verification.mjs +4 -3
- package/dist/api/routes/session.mjs +14 -9
- package/dist/api/routes/sign-in.d.mts +1 -0
- package/dist/api/routes/sign-in.mjs +2 -1
- package/dist/api/routes/sign-up.d.mts +1 -0
- package/dist/api/routes/sign-up.mjs +9 -7
- package/dist/api/routes/update-user.mjs +5 -5
- package/dist/client/index.d.mts +2 -2
- package/dist/client/parser.mjs +0 -1
- package/dist/client/plugins/index.d.mts +3 -3
- package/dist/client/proxy.mjs +2 -1
- package/dist/context/helpers.mjs +3 -2
- package/dist/cookies/cookie-utils.d.mts +24 -1
- package/dist/cookies/cookie-utils.mjs +85 -22
- package/dist/cookies/index.d.mts +2 -3
- package/dist/cookies/index.mjs +39 -11
- package/dist/cookies/session-store.mjs +4 -23
- package/dist/db/get-migration.mjs +4 -4
- package/dist/db/index.d.mts +2 -2
- package/dist/db/index.mjs +3 -2
- package/dist/db/internal-adapter.mjs +96 -1
- package/dist/db/schema.d.mts +15 -2
- package/dist/db/schema.mjs +26 -1
- package/dist/db/with-hooks.d.mts +1 -0
- package/dist/db/with-hooks.mjs +58 -1
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +2 -2
- package/dist/oauth2/errors.mjs +16 -1
- package/dist/oauth2/link-account.mjs +6 -4
- package/dist/oauth2/state.mjs +8 -2
- package/dist/package.mjs +1 -1
- package/dist/plugins/access/access.d.mts +3 -15
- package/dist/plugins/access/access.mjs +11 -6
- package/dist/plugins/access/index.d.mts +2 -2
- package/dist/plugins/access/types.d.mts +11 -4
- package/dist/plugins/admin/access/statement.d.mts +29 -93
- package/dist/plugins/admin/admin.mjs +0 -4
- package/dist/plugins/admin/client.d.mts +1 -1
- package/dist/plugins/admin/routes.mjs +1 -0
- package/dist/plugins/anonymous/client.d.mts +1 -0
- package/dist/plugins/anonymous/error-codes.d.mts +1 -0
- package/dist/plugins/anonymous/error-codes.mjs +1 -0
- package/dist/plugins/anonymous/index.d.mts +1 -0
- package/dist/plugins/anonymous/index.mjs +16 -2
- package/dist/plugins/bearer/index.mjs +4 -9
- package/dist/plugins/captcha/index.mjs +2 -2
- package/dist/plugins/device-authorization/error-codes.mjs +1 -0
- package/dist/plugins/device-authorization/index.d.mts +1 -0
- package/dist/plugins/device-authorization/routes.mjs +34 -3
- package/dist/plugins/generic-oauth/index.d.mts +1 -1
- package/dist/plugins/generic-oauth/index.mjs +6 -6
- package/dist/plugins/generic-oauth/routes.mjs +34 -32
- package/dist/plugins/generic-oauth/types.d.mts +7 -0
- package/dist/plugins/index.d.mts +2 -2
- package/dist/plugins/last-login-method/client.mjs +2 -2
- package/dist/plugins/magic-link/index.d.mts +8 -1
- package/dist/plugins/magic-link/index.mjs +4 -17
- package/dist/plugins/mcp/authorize.mjs +8 -2
- package/dist/plugins/mcp/index.mjs +73 -34
- package/dist/plugins/multi-session/index.mjs +2 -2
- package/dist/plugins/oauth-proxy/index.mjs +44 -31
- package/dist/plugins/oauth-proxy/utils.mjs +3 -10
- package/dist/plugins/oidc-provider/authorize.mjs +8 -2
- package/dist/plugins/oidc-provider/index.mjs +63 -37
- package/dist/plugins/one-tap/index.mjs +13 -8
- package/dist/plugins/open-api/generator.mjs +16 -5
- package/dist/plugins/organization/access/statement.d.mts +68 -201
- package/dist/plugins/organization/adapter.mjs +61 -56
- package/dist/plugins/organization/client.d.mts +3 -1
- package/dist/plugins/organization/error-codes.d.mts +2 -0
- package/dist/plugins/organization/error-codes.mjs +3 -1
- package/dist/plugins/organization/routes/crud-access-control.d.mts +2 -2
- package/dist/plugins/organization/routes/crud-invites.mjs +7 -2
- package/dist/plugins/organization/types.d.mts +12 -2
- package/dist/plugins/two-factor/index.mjs +3 -2
- package/dist/plugins/username/index.d.mts +24 -2
- package/dist/plugins/username/index.mjs +49 -3
- package/dist/state.d.mts +2 -2
- package/dist/state.mjs +18 -4
- package/dist/test-utils/headers.mjs +2 -7
- package/dist/test-utils/test-instance.d.mts +25 -6
- package/dist/test-utils/test-instance.mjs +11 -2
- package/dist/utils/index.d.mts +1 -1
- package/dist/utils/url.d.mts +2 -1
- package/dist/utils/url.mjs +9 -3
- package/package.json +15 -14
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { getBaseURL, getHost, getHostFromSource, getOrigin, getProtocol, getProtocolFromSource, isDynamicBaseURLConfig, isRequestLike, matchesHostPattern, resolveBaseURL, resolveDynamicBaseURL } from "./utils/url.mjs";
|
|
1
|
+
import { getBaseURL, getHost, getHostFromSource, getOrigin, getProtocol, getProtocolFromSource, isDynamicBaseURLConfig, isRequestLike, matchesHostPattern, resolveBaseURL, resolveDynamicBaseURL, trimTrailingSlashes } from "./utils/url.mjs";
|
|
2
2
|
import { generateGenericState, parseGenericState } from "./state.mjs";
|
|
3
3
|
import { generateState, parseState } from "./oauth2/state.mjs";
|
|
4
4
|
import { HIDE_METADATA } from "./utils/hide-metadata.mjs";
|
|
@@ -14,4 +14,4 @@ export * from "@better-auth/core/oauth2";
|
|
|
14
14
|
export * from "@better-auth/core/utils/error-codes";
|
|
15
15
|
export * from "@better-auth/core/utils/id";
|
|
16
16
|
export * from "@better-auth/core/utils/json";
|
|
17
|
-
export { APIError, HIDE_METADATA, betterAuth, createTelemetry, generateGenericState, generateState, getBaseURL, getCurrentAdapter, getHost, getHostFromSource, getOrigin, getProtocol, getProtocolFromSource, getTelemetryAuthConfig, isDynamicBaseURLConfig, isRequestLike, matchesHostPattern, parseGenericState, parseState, resolveBaseURL, resolveDynamicBaseURL };
|
|
17
|
+
export { APIError, HIDE_METADATA, betterAuth, createTelemetry, generateGenericState, generateState, getBaseURL, getCurrentAdapter, getHost, getHostFromSource, getOrigin, getProtocol, getProtocolFromSource, getTelemetryAuthConfig, isDynamicBaseURLConfig, isRequestLike, matchesHostPattern, parseGenericState, parseState, resolveBaseURL, resolveDynamicBaseURL, trimTrailingSlashes };
|
package/dist/oauth2/errors.mjs
CHANGED
|
@@ -1,6 +1,21 @@
|
|
|
1
1
|
//#region src/oauth2/errors.ts
|
|
2
2
|
const HANDLING_DOCS_URL = "https://www.better-auth.com/docs/concepts/oauth#handling-providers-without-email";
|
|
3
3
|
/**
|
|
4
|
+
* Redirect the user to the OAuth error page with a machine-readable `error`
|
|
5
|
+
* code (and optional `error_description`).
|
|
6
|
+
*
|
|
7
|
+
* Every OAuth callback path routes its failures through this helper so the
|
|
8
|
+
* query parameter name, the `?`/`&` separator, and URL encoding are decided in
|
|
9
|
+
* one place. The error page reads the `error` query parameter, so callers must
|
|
10
|
+
* never hand-build the redirect with a different parameter name.
|
|
11
|
+
*/
|
|
12
|
+
function redirectOnError(ctx, errorURL, error, description) {
|
|
13
|
+
const params = new URLSearchParams({ error });
|
|
14
|
+
if (description) params.set("error_description", description);
|
|
15
|
+
const sep = errorURL.includes("?") ? "&" : "?";
|
|
16
|
+
throw ctx.redirect(`${errorURL}${sep}${params.toString()}`);
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
4
19
|
* Build the logger message shown when an OAuth provider does not return an
|
|
5
20
|
* email address. Kept in one place so every rejection site points users at
|
|
6
21
|
* the same workaround docs.
|
|
@@ -9,4 +24,4 @@ function missingEmailLogMessage(providerId, options) {
|
|
|
9
24
|
return `${options?.source === "generic" ? `Generic OAuth provider "${providerId}"` : `Provider "${providerId}"`} did not return an email${options?.source === "id_token" ? " in the id token" : ""}. Either request the provider's email scope, or synthesize one via \`mapProfileToUser\`. See ${HANDLING_DOCS_URL}`;
|
|
10
25
|
}
|
|
11
26
|
//#endregion
|
|
12
|
-
export { missingEmailLogMessage };
|
|
27
|
+
export { missingEmailLogMessage, redirectOnError };
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { isAPIError } from "../utils/is-api-error.mjs";
|
|
2
2
|
import { setAccountCookie } from "../cookies/session-store.mjs";
|
|
3
|
+
import { redirectOnError } from "./errors.mjs";
|
|
3
4
|
import { setTokenUtil } from "./utils.mjs";
|
|
4
5
|
import { createEmailVerificationToken } from "../api/routes/email-verification.mjs";
|
|
5
6
|
import { isDevelopment, logger } from "@better-auth/core/env";
|
|
@@ -8,8 +9,7 @@ async function handleOAuthUserInfo(c, opts) {
|
|
|
8
9
|
const { userInfo, account, callbackURL, disableSignUp, overrideUserInfo } = opts;
|
|
9
10
|
const dbUser = await c.context.internalAdapter.findOAuthUser(userInfo.email.toLowerCase(), account.accountId, account.providerId).catch((e) => {
|
|
10
11
|
logger.error("Better auth was unable to query your database.\nError: ", e);
|
|
11
|
-
|
|
12
|
-
throw c.redirect(`${errorURL}?error=internal_server_error`);
|
|
12
|
+
redirectOnError(c, c.context.options.onAPIError?.errorURL || `${c.context.baseURL}/error`, "internal_server_error");
|
|
13
13
|
});
|
|
14
14
|
let user = dbUser?.user;
|
|
15
15
|
const isRegister = !user;
|
|
@@ -17,7 +17,9 @@ async function handleOAuthUserInfo(c, opts) {
|
|
|
17
17
|
const linkedAccount = dbUser.linkedAccount ?? dbUser.accounts.find((acc) => acc.providerId === account.providerId && acc.accountId === account.accountId);
|
|
18
18
|
if (!linkedAccount) {
|
|
19
19
|
const accountLinking = c.context.options.account?.accountLinking;
|
|
20
|
-
|
|
20
|
+
const isTrustedProvider = opts.isTrustedProvider || c.context.trustedProviders.includes(account.providerId);
|
|
21
|
+
const requireLocalEmailVerified = accountLinking?.requireLocalEmailVerified ?? true;
|
|
22
|
+
if (!isTrustedProvider && !userInfo.emailVerified || requireLocalEmailVerified && !dbUser.user.emailVerified || accountLinking?.enabled === false || accountLinking?.disableImplicitLinking === true) {
|
|
21
23
|
if (isDevelopment()) logger.warn(`User already exist but account isn't linked to ${account.providerId}. To read more about how account linking works in Better Auth see https://www.better-auth.com/docs/concepts/users-accounts#account-linking.`);
|
|
22
24
|
return {
|
|
23
25
|
error: "account not linked",
|
|
@@ -94,7 +96,7 @@ async function handleOAuthUserInfo(c, opts) {
|
|
|
94
96
|
if (c.context.options.account?.storeAccountCookie) await setAccountCookie(c, createdAccount);
|
|
95
97
|
if (!userInfo.emailVerified && user && c.context.options.emailVerification?.sendOnSignUp && c.context.options.emailVerification?.sendVerificationEmail) {
|
|
96
98
|
const token = await createEmailVerificationToken(c.context.secret, user.email, void 0, c.context.options.emailVerification?.expiresIn);
|
|
97
|
-
const url = `${c.context.baseURL}/verify-email?token=${token}&callbackURL=${callbackURL}`;
|
|
99
|
+
const url = `${c.context.baseURL}/verify-email?token=${token}&callbackURL=${encodeURIComponent(callbackURL || "/")}`;
|
|
98
100
|
await c.context.runInBackgroundOrAwait(c.context.options.emailVerification.sendVerificationEmail({
|
|
99
101
|
user,
|
|
100
102
|
url,
|
package/dist/oauth2/state.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { generateRandomString } from "../crypto/random.mjs";
|
|
2
|
+
import { redirectOnError } from "./errors.mjs";
|
|
2
3
|
import { setOAuthState } from "../api/state/oauth.mjs";
|
|
3
4
|
import { StateError, generateGenericState, parseGenericState } from "../state.mjs";
|
|
4
5
|
import { APIError, BASE_ERROR_CODES } from "@better-auth/core/error";
|
|
@@ -36,8 +37,13 @@ async function parseState(c) {
|
|
|
36
37
|
parsedData = await parseGenericState(c, state);
|
|
37
38
|
} catch (error) {
|
|
38
39
|
c.context.logger.error("Failed to parse state", error);
|
|
39
|
-
|
|
40
|
-
|
|
40
|
+
let code = "internal_server_error";
|
|
41
|
+
let redirectErrorURL = errorURL;
|
|
42
|
+
if (error instanceof StateError) {
|
|
43
|
+
code = error.code === "state_security_mismatch" ? "state_mismatch" : error.code;
|
|
44
|
+
redirectErrorURL = error.errorURL ?? errorURL;
|
|
45
|
+
}
|
|
46
|
+
redirectOnError(c, redirectErrorURL, code);
|
|
41
47
|
}
|
|
42
48
|
if (!parsedData.errorURL) parsedData.errorURL = errorURL;
|
|
43
49
|
if (parsedData) await setOAuthState(parsedData);
|
package/dist/package.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ExactRoleStatements, Role, RoleInput, Statements } from "./types.mjs";
|
|
2
2
|
|
|
3
3
|
//#region src/plugins/access/access.d.ts
|
|
4
4
|
type AuthorizeResponse = {
|
|
@@ -8,21 +8,9 @@ type AuthorizeResponse = {
|
|
|
8
8
|
success: true;
|
|
9
9
|
error?: never | undefined;
|
|
10
10
|
};
|
|
11
|
-
declare function role<
|
|
12
|
-
authorize<K extends keyof TStatements>(request: { [key in K]?: TStatements[key] | {
|
|
13
|
-
actions: TStatements[key];
|
|
14
|
-
connector: "OR" | "AND";
|
|
15
|
-
} }, connector?: "OR" | "AND"): AuthorizeResponse;
|
|
16
|
-
statements: TStatements;
|
|
17
|
-
};
|
|
11
|
+
declare function role<const TRoleStatements extends Statements, TAuthorizeStatements extends Statements = TRoleStatements>(statements: TRoleStatements): Role<ExactRoleStatements<TRoleStatements>, TAuthorizeStatements>;
|
|
18
12
|
declare function createAccessControl<const TStatements extends Statements>(s: TStatements): {
|
|
19
|
-
newRole<
|
|
20
|
-
authorize<K_1 extends K>(request: K_1 extends infer T extends keyof Subset<K, TStatements> ? { [key in T]?: Subset<K, TStatements>[key] | {
|
|
21
|
-
actions: Subset<K, TStatements>[key];
|
|
22
|
-
connector: "OR" | "AND";
|
|
23
|
-
} | undefined } : never, connector?: "OR" | "AND"): AuthorizeResponse;
|
|
24
|
-
statements: Subset<K, TStatements>;
|
|
25
|
-
};
|
|
13
|
+
newRole<const TRoleStatements extends Statements>(statements: RoleInput<TStatements, TRoleStatements>): Role<ExactRoleStatements<TRoleStatements>, TStatements>;
|
|
26
14
|
statements: TStatements;
|
|
27
15
|
};
|
|
28
16
|
//#endregion
|
|
@@ -6,14 +6,19 @@ function role(statements) {
|
|
|
6
6
|
let success = false;
|
|
7
7
|
for (const [requestedResource, requestedActions] of Object.entries(request)) {
|
|
8
8
|
const allowedActions = statements[requestedResource];
|
|
9
|
-
if (!allowedActions)
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
9
|
+
if (!allowedActions) {
|
|
10
|
+
if (connector === "AND") return {
|
|
11
|
+
success: false,
|
|
12
|
+
error: `You are not allowed to access resource: ${requestedResource}`
|
|
13
|
+
};
|
|
14
|
+
success = false;
|
|
15
|
+
continue;
|
|
16
|
+
}
|
|
17
|
+
if (Array.isArray(requestedActions)) success = requestedActions.length > 0 && requestedActions.every((requestedAction) => allowedActions.includes(requestedAction));
|
|
14
18
|
else if (typeof requestedActions === "object") {
|
|
15
19
|
const actions = requestedActions;
|
|
16
|
-
if (actions.
|
|
20
|
+
if (!Array.isArray(actions.actions) || actions.actions.length === 0) success = false;
|
|
21
|
+
else if (actions.connector === "OR") success = actions.actions.some((requestedAction) => allowedActions.includes(requestedAction));
|
|
17
22
|
else success = actions.actions.every((requestedAction) => allowedActions.includes(requestedAction));
|
|
18
23
|
} else throw new BetterAuthError("Invalid access control request");
|
|
19
24
|
if (success && connector === "OR") return { success };
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { AccessControl, ArrayElement, Role, Statements, SubArray, Subset } from "./types.mjs";
|
|
1
|
+
import { AccessControl, ArrayElement, ExactRoleStatements, Role, RoleAuthorizeRequest, RoleInput, RoleStatements, Statements, SubArray, Subset } from "./types.mjs";
|
|
2
2
|
import { AuthorizeResponse, createAccessControl, role } from "./access.mjs";
|
|
3
|
-
export { AccessControl, ArrayElement, AuthorizeResponse, Role, Statements, SubArray, Subset, createAccessControl, role };
|
|
3
|
+
export { AccessControl, ArrayElement, AuthorizeResponse, ExactRoleStatements, Role, RoleAuthorizeRequest, RoleInput, RoleStatements, Statements, SubArray, Subset, createAccessControl, role };
|
|
@@ -8,10 +8,17 @@ type Subset<K extends keyof R, R extends Record<string | LiteralString, readonly
|
|
|
8
8
|
type Statements = {
|
|
9
9
|
readonly [resource: string]: readonly LiteralString[];
|
|
10
10
|
};
|
|
11
|
+
type RoleStatements<TStatements extends Statements> = { readonly [P in keyof TStatements]?: SubArray<TStatements[P]> };
|
|
12
|
+
type RoleInput<TStatements extends Statements, TRoleStatements extends Statements> = TRoleStatements & (string extends keyof TRoleStatements ? {} : RoleStatements<TStatements> & Record<Exclude<keyof TRoleStatements, keyof TStatements>, never>);
|
|
13
|
+
type ExactRoleStatements<TStatements extends Statements> = { readonly [P in keyof TStatements]: readonly [...TStatements[P]] };
|
|
11
14
|
type AccessControl<TStatements extends Statements = Statements> = ReturnType<typeof createAccessControl<TStatements>>;
|
|
12
|
-
type
|
|
13
|
-
|
|
14
|
-
|
|
15
|
+
type RoleAuthorizeRequest<TStatements extends Statements> = { [P in keyof TStatements]?: SubArray<TStatements[P]> | {
|
|
16
|
+
actions: SubArray<TStatements[P]>;
|
|
17
|
+
connector: "OR" | "AND";
|
|
18
|
+
} };
|
|
19
|
+
type Role<TRoleStatements extends Statements = Record<string, any>, TAuthorizeStatements extends Statements = TRoleStatements> = {
|
|
20
|
+
authorize: (request: RoleAuthorizeRequest<TAuthorizeStatements>, connector?: ("OR" | "AND") | undefined) => AuthorizeResponse;
|
|
21
|
+
statements: TRoleStatements;
|
|
15
22
|
};
|
|
16
23
|
//#endregion
|
|
17
|
-
export { AccessControl, ArrayElement, Role, Statements, SubArray, Subset };
|
|
24
|
+
export { AccessControl, ArrayElement, ExactRoleStatements, Role, RoleAuthorizeRequest, RoleInput, RoleStatements, Statements, SubArray, Subset };
|
|
@@ -1,115 +1,51 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { AuthorizeResponse } from "../../access/access.mjs";
|
|
1
|
+
import { ExactRoleStatements, Role, RoleInput, Statements } from "../../access/types.mjs";
|
|
3
2
|
//#region src/plugins/admin/access/statement.d.ts
|
|
4
3
|
declare const defaultStatements: {
|
|
5
4
|
readonly user: readonly ["create", "list", "set-role", "ban", "impersonate", "impersonate-admins", "delete", "set-password", "get", "update"];
|
|
6
5
|
readonly session: readonly ["list", "revoke", "delete"];
|
|
7
6
|
};
|
|
8
7
|
declare const defaultAc: {
|
|
9
|
-
newRole<
|
|
8
|
+
newRole<const TRoleStatements extends Statements>(statements: RoleInput<{
|
|
10
9
|
readonly user: readonly ["create", "list", "set-role", "ban", "impersonate", "impersonate-admins", "delete", "set-password", "get", "update"];
|
|
11
10
|
readonly session: readonly ["list", "revoke", "delete"];
|
|
12
|
-
}>): {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
}> ? { [key in T]?: Subset<K, {
|
|
17
|
-
readonly user: readonly ["create", "list", "set-role", "ban", "impersonate", "impersonate-admins", "delete", "set-password", "get", "update"];
|
|
18
|
-
readonly session: readonly ["list", "revoke", "delete"];
|
|
19
|
-
}>[key] | {
|
|
20
|
-
actions: Subset<K, {
|
|
21
|
-
readonly user: readonly ["create", "list", "set-role", "ban", "impersonate", "impersonate-admins", "delete", "set-password", "get", "update"];
|
|
22
|
-
readonly session: readonly ["list", "revoke", "delete"];
|
|
23
|
-
}>[key];
|
|
24
|
-
connector: "OR" | "AND";
|
|
25
|
-
} | undefined } : never, connector?: "OR" | "AND"): AuthorizeResponse;
|
|
26
|
-
statements: Subset<K, {
|
|
27
|
-
readonly user: readonly ["create", "list", "set-role", "ban", "impersonate", "impersonate-admins", "delete", "set-password", "get", "update"];
|
|
28
|
-
readonly session: readonly ["list", "revoke", "delete"];
|
|
29
|
-
}>;
|
|
30
|
-
};
|
|
11
|
+
}, TRoleStatements>): Role<ExactRoleStatements<TRoleStatements>, {
|
|
12
|
+
readonly user: readonly ["create", "list", "set-role", "ban", "impersonate", "impersonate-admins", "delete", "set-password", "get", "update"];
|
|
13
|
+
readonly session: readonly ["list", "revoke", "delete"];
|
|
14
|
+
}>;
|
|
31
15
|
statements: {
|
|
32
16
|
readonly user: readonly ["create", "list", "set-role", "ban", "impersonate", "impersonate-admins", "delete", "set-password", "get", "update"];
|
|
33
17
|
readonly session: readonly ["list", "revoke", "delete"];
|
|
34
18
|
};
|
|
35
19
|
};
|
|
36
|
-
declare const adminAc: {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
20
|
+
declare const adminAc: Role<ExactRoleStatements<{
|
|
21
|
+
readonly user: ["create", "list", "set-role", "ban", "impersonate", "delete", "set-password", "get", "update"];
|
|
22
|
+
readonly session: ["list", "revoke", "delete"];
|
|
23
|
+
}>, {
|
|
24
|
+
readonly user: readonly ["create", "list", "set-role", "ban", "impersonate", "impersonate-admins", "delete", "set-password", "get", "update"];
|
|
25
|
+
readonly session: readonly ["list", "revoke", "delete"];
|
|
26
|
+
}>;
|
|
27
|
+
declare const userAc: Role<ExactRoleStatements<{
|
|
28
|
+
readonly user: [];
|
|
29
|
+
readonly session: [];
|
|
30
|
+
}>, {
|
|
31
|
+
readonly user: readonly ["create", "list", "set-role", "ban", "impersonate", "impersonate-admins", "delete", "set-password", "get", "update"];
|
|
32
|
+
readonly session: readonly ["list", "revoke", "delete"];
|
|
33
|
+
}>;
|
|
34
|
+
declare const defaultRoles: {
|
|
35
|
+
admin: Role<ExactRoleStatements<{
|
|
36
|
+
readonly user: ["create", "list", "set-role", "ban", "impersonate", "delete", "set-password", "get", "update"];
|
|
37
|
+
readonly session: ["list", "revoke", "delete"];
|
|
38
|
+
}>, {
|
|
51
39
|
readonly user: readonly ["create", "list", "set-role", "ban", "impersonate", "impersonate-admins", "delete", "set-password", "get", "update"];
|
|
52
40
|
readonly session: readonly ["list", "revoke", "delete"];
|
|
53
41
|
}>;
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
readonly session: readonly ["list", "revoke", "delete"];
|
|
59
|
-
}> ? { [key in T]?: Subset<"user" | "session", {
|
|
60
|
-
readonly user: readonly ["create", "list", "set-role", "ban", "impersonate", "impersonate-admins", "delete", "set-password", "get", "update"];
|
|
61
|
-
readonly session: readonly ["list", "revoke", "delete"];
|
|
62
|
-
}>[key] | {
|
|
63
|
-
actions: Subset<"user" | "session", {
|
|
64
|
-
readonly user: readonly ["create", "list", "set-role", "ban", "impersonate", "impersonate-admins", "delete", "set-password", "get", "update"];
|
|
65
|
-
readonly session: readonly ["list", "revoke", "delete"];
|
|
66
|
-
}>[key];
|
|
67
|
-
connector: "OR" | "AND";
|
|
68
|
-
} | undefined } : never, connector?: "OR" | "AND"): AuthorizeResponse;
|
|
69
|
-
statements: Subset<"user" | "session", {
|
|
42
|
+
user: Role<ExactRoleStatements<{
|
|
43
|
+
readonly user: [];
|
|
44
|
+
readonly session: [];
|
|
45
|
+
}>, {
|
|
70
46
|
readonly user: readonly ["create", "list", "set-role", "ban", "impersonate", "impersonate-admins", "delete", "set-password", "get", "update"];
|
|
71
47
|
readonly session: readonly ["list", "revoke", "delete"];
|
|
72
48
|
}>;
|
|
73
49
|
};
|
|
74
|
-
declare const defaultRoles: {
|
|
75
|
-
admin: {
|
|
76
|
-
authorize<K extends "user" | "session">(request: K extends infer T extends keyof Subset<"user" | "session", {
|
|
77
|
-
readonly user: readonly ["create", "list", "set-role", "ban", "impersonate", "impersonate-admins", "delete", "set-password", "get", "update"];
|
|
78
|
-
readonly session: readonly ["list", "revoke", "delete"];
|
|
79
|
-
}> ? { [key in T]?: Subset<"user" | "session", {
|
|
80
|
-
readonly user: readonly ["create", "list", "set-role", "ban", "impersonate", "impersonate-admins", "delete", "set-password", "get", "update"];
|
|
81
|
-
readonly session: readonly ["list", "revoke", "delete"];
|
|
82
|
-
}>[key] | {
|
|
83
|
-
actions: Subset<"user" | "session", {
|
|
84
|
-
readonly user: readonly ["create", "list", "set-role", "ban", "impersonate", "impersonate-admins", "delete", "set-password", "get", "update"];
|
|
85
|
-
readonly session: readonly ["list", "revoke", "delete"];
|
|
86
|
-
}>[key];
|
|
87
|
-
connector: "OR" | "AND";
|
|
88
|
-
} | undefined } : never, connector?: "OR" | "AND"): AuthorizeResponse;
|
|
89
|
-
statements: Subset<"user" | "session", {
|
|
90
|
-
readonly user: readonly ["create", "list", "set-role", "ban", "impersonate", "impersonate-admins", "delete", "set-password", "get", "update"];
|
|
91
|
-
readonly session: readonly ["list", "revoke", "delete"];
|
|
92
|
-
}>;
|
|
93
|
-
};
|
|
94
|
-
user: {
|
|
95
|
-
authorize<K extends "user" | "session">(request: K extends infer T extends keyof Subset<"user" | "session", {
|
|
96
|
-
readonly user: readonly ["create", "list", "set-role", "ban", "impersonate", "impersonate-admins", "delete", "set-password", "get", "update"];
|
|
97
|
-
readonly session: readonly ["list", "revoke", "delete"];
|
|
98
|
-
}> ? { [key in T]?: Subset<"user" | "session", {
|
|
99
|
-
readonly user: readonly ["create", "list", "set-role", "ban", "impersonate", "impersonate-admins", "delete", "set-password", "get", "update"];
|
|
100
|
-
readonly session: readonly ["list", "revoke", "delete"];
|
|
101
|
-
}>[key] | {
|
|
102
|
-
actions: Subset<"user" | "session", {
|
|
103
|
-
readonly user: readonly ["create", "list", "set-role", "ban", "impersonate", "impersonate-admins", "delete", "set-password", "get", "update"];
|
|
104
|
-
readonly session: readonly ["list", "revoke", "delete"];
|
|
105
|
-
}>[key];
|
|
106
|
-
connector: "OR" | "AND";
|
|
107
|
-
} | undefined } : never, connector?: "OR" | "AND"): AuthorizeResponse;
|
|
108
|
-
statements: Subset<"user" | "session", {
|
|
109
|
-
readonly user: readonly ["create", "list", "set-role", "ban", "impersonate", "impersonate-admins", "delete", "set-password", "get", "update"];
|
|
110
|
-
readonly session: readonly ["list", "revoke", "delete"];
|
|
111
|
-
}>;
|
|
112
|
-
};
|
|
113
|
-
};
|
|
114
50
|
//#endregion
|
|
115
51
|
export { adminAc, defaultAc, defaultRoles, defaultStatements, userAc };
|
|
@@ -42,10 +42,6 @@ const admin = (options) => {
|
|
|
42
42
|
});
|
|
43
43
|
return;
|
|
44
44
|
}
|
|
45
|
-
if (ctx && (ctx.path.startsWith("/callback") || ctx.path.startsWith("/oauth2/callback"))) {
|
|
46
|
-
const redirectURI = ctx.context.options.onAPIError?.errorURL || `${ctx.context.baseURL}/error`;
|
|
47
|
-
throw ctx.redirect(`${redirectURI}?error=banned&error_description=${opts.bannedUserMessage}`);
|
|
48
|
-
}
|
|
49
45
|
throw APIError.from("FORBIDDEN", {
|
|
50
46
|
message: opts.bannedUserMessage,
|
|
51
47
|
code: "BANNED_USER"
|
|
@@ -703,6 +703,7 @@ const removeUser = (opts) => createAuthEndpoint("/admin/remove-user", {
|
|
|
703
703
|
})) throw APIError.from("FORBIDDEN", ADMIN_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_DELETE_USERS);
|
|
704
704
|
if (ctx.body.userId === ctx.context.session.user.id) throw APIError.from("BAD_REQUEST", ADMIN_ERROR_CODES.YOU_CANNOT_REMOVE_YOURSELF);
|
|
705
705
|
if (!await ctx.context.internalAdapter.findUserById(ctx.body.userId)) throw APIError.from("NOT_FOUND", BASE_ERROR_CODES.USER_NOT_FOUND);
|
|
706
|
+
await ctx.context.internalAdapter.deleteSessions(ctx.body.userId);
|
|
706
707
|
await ctx.context.internalAdapter.deleteUser(ctx.body.userId);
|
|
707
708
|
return ctx.json({ success: true });
|
|
708
709
|
});
|
|
@@ -23,6 +23,7 @@ declare const anonymousClient: () => {
|
|
|
23
23
|
COULD_NOT_CREATE_SESSION: _better_auth_core_utils_error_codes0.RawError<"COULD_NOT_CREATE_SESSION">;
|
|
24
24
|
ANONYMOUS_USERS_CANNOT_SIGN_IN_AGAIN_ANONYMOUSLY: _better_auth_core_utils_error_codes0.RawError<"ANONYMOUS_USERS_CANNOT_SIGN_IN_AGAIN_ANONYMOUSLY">;
|
|
25
25
|
FAILED_TO_DELETE_ANONYMOUS_USER: _better_auth_core_utils_error_codes0.RawError<"FAILED_TO_DELETE_ANONYMOUS_USER">;
|
|
26
|
+
FAILED_TO_DELETE_ANONYMOUS_USER_SESSIONS: _better_auth_core_utils_error_codes0.RawError<"FAILED_TO_DELETE_ANONYMOUS_USER_SESSIONS">;
|
|
26
27
|
USER_IS_NOT_ANONYMOUS: _better_auth_core_utils_error_codes0.RawError<"USER_IS_NOT_ANONYMOUS">;
|
|
27
28
|
DELETE_ANONYMOUS_USER_DISABLED: _better_auth_core_utils_error_codes0.RawError<"DELETE_ANONYMOUS_USER_DISABLED">;
|
|
28
29
|
};
|
|
@@ -7,6 +7,7 @@ declare const ANONYMOUS_ERROR_CODES: {
|
|
|
7
7
|
COULD_NOT_CREATE_SESSION: _better_auth_core_utils_error_codes0.RawError<"COULD_NOT_CREATE_SESSION">;
|
|
8
8
|
ANONYMOUS_USERS_CANNOT_SIGN_IN_AGAIN_ANONYMOUSLY: _better_auth_core_utils_error_codes0.RawError<"ANONYMOUS_USERS_CANNOT_SIGN_IN_AGAIN_ANONYMOUSLY">;
|
|
9
9
|
FAILED_TO_DELETE_ANONYMOUS_USER: _better_auth_core_utils_error_codes0.RawError<"FAILED_TO_DELETE_ANONYMOUS_USER">;
|
|
10
|
+
FAILED_TO_DELETE_ANONYMOUS_USER_SESSIONS: _better_auth_core_utils_error_codes0.RawError<"FAILED_TO_DELETE_ANONYMOUS_USER_SESSIONS">;
|
|
10
11
|
USER_IS_NOT_ANONYMOUS: _better_auth_core_utils_error_codes0.RawError<"USER_IS_NOT_ANONYMOUS">;
|
|
11
12
|
DELETE_ANONYMOUS_USER_DISABLED: _better_auth_core_utils_error_codes0.RawError<"DELETE_ANONYMOUS_USER_DISABLED">;
|
|
12
13
|
};
|
|
@@ -6,6 +6,7 @@ const ANONYMOUS_ERROR_CODES = defineErrorCodes({
|
|
|
6
6
|
COULD_NOT_CREATE_SESSION: "Could not create session",
|
|
7
7
|
ANONYMOUS_USERS_CANNOT_SIGN_IN_AGAIN_ANONYMOUSLY: "Anonymous users cannot sign in again anonymously",
|
|
8
8
|
FAILED_TO_DELETE_ANONYMOUS_USER: "Failed to delete anonymous user",
|
|
9
|
+
FAILED_TO_DELETE_ANONYMOUS_USER_SESSIONS: "Failed to delete anonymous user sessions",
|
|
9
10
|
USER_IS_NOT_ANONYMOUS: "User is not anonymous",
|
|
10
11
|
DELETE_ANONYMOUS_USER_DISABLED: "Deleting anonymous users is disabled"
|
|
11
12
|
});
|
|
@@ -162,6 +162,7 @@ declare const anonymous: (options?: AnonymousOptions | undefined) => {
|
|
|
162
162
|
COULD_NOT_CREATE_SESSION: _better_auth_core_utils_error_codes0.RawError<"COULD_NOT_CREATE_SESSION">;
|
|
163
163
|
ANONYMOUS_USERS_CANNOT_SIGN_IN_AGAIN_ANONYMOUSLY: _better_auth_core_utils_error_codes0.RawError<"ANONYMOUS_USERS_CANNOT_SIGN_IN_AGAIN_ANONYMOUSLY">;
|
|
164
164
|
FAILED_TO_DELETE_ANONYMOUS_USER: _better_auth_core_utils_error_codes0.RawError<"FAILED_TO_DELETE_ANONYMOUS_USER">;
|
|
165
|
+
FAILED_TO_DELETE_ANONYMOUS_USER_SESSIONS: _better_auth_core_utils_error_codes0.RawError<"FAILED_TO_DELETE_ANONYMOUS_USER_SESSIONS">;
|
|
165
166
|
USER_IS_NOT_ANONYMOUS: _better_auth_core_utils_error_codes0.RawError<"USER_IS_NOT_ANONYMOUS">;
|
|
166
167
|
DELETE_ANONYMOUS_USER_DISABLED: _better_auth_core_utils_error_codes0.RawError<"DELETE_ANONYMOUS_USER_DISABLED">;
|
|
167
168
|
};
|
|
@@ -101,6 +101,12 @@ const anonymous = (options) => {
|
|
|
101
101
|
const session = ctx.context.session;
|
|
102
102
|
if (options?.disableDeleteAnonymousUser) throw APIError.from("BAD_REQUEST", ANONYMOUS_ERROR_CODES.DELETE_ANONYMOUS_USER_DISABLED);
|
|
103
103
|
if (!session.user.isAnonymous) throw APIError.from("FORBIDDEN", ANONYMOUS_ERROR_CODES.USER_IS_NOT_ANONYMOUS);
|
|
104
|
+
try {
|
|
105
|
+
await ctx.context.internalAdapter.deleteSessions(session.user.id);
|
|
106
|
+
} catch (error) {
|
|
107
|
+
ctx.context.logger.error("Failed to delete anonymous user sessions", error);
|
|
108
|
+
throw APIError.from("INTERNAL_SERVER_ERROR", ANONYMOUS_ERROR_CODES.FAILED_TO_DELETE_ANONYMOUS_USER_SESSIONS);
|
|
109
|
+
}
|
|
104
110
|
try {
|
|
105
111
|
await ctx.context.internalAdapter.deleteUser(session.user.id);
|
|
106
112
|
} catch (error) {
|
|
@@ -113,7 +119,7 @@ const anonymous = (options) => {
|
|
|
113
119
|
},
|
|
114
120
|
hooks: { after: [{
|
|
115
121
|
matcher(ctx) {
|
|
116
|
-
return ctx.path?.startsWith("/sign-in") || ctx.path?.startsWith("/sign-up") || ctx.path?.startsWith("/callback") || ctx.path?.startsWith("/oauth2/callback") || ctx.path?.startsWith("/magic-link/verify") || ctx.path?.startsWith("/email-otp/verify-email") || ctx.path?.startsWith("/one-tap/callback") || ctx.path?.startsWith("/passkey/verify-authentication") || ctx.path?.startsWith("/phone-number/verify") || false;
|
|
122
|
+
return ctx.path?.startsWith("/sign-in") || ctx.path?.startsWith("/sign-up") || ctx.path?.startsWith("/callback") || ctx.path?.startsWith("/oauth2/callback") || ctx.path?.startsWith("/magic-link/verify") || ctx.path?.startsWith("/email-otp/verify-email") || ctx.path?.startsWith("/one-tap/callback") || ctx.path?.startsWith("/passkey/verify-authentication") || ctx.path?.startsWith("/phone-number/verify") || ctx.path?.startsWith("/verify-email") || false;
|
|
117
123
|
},
|
|
118
124
|
handler: createAuthMiddleware(async (ctx) => {
|
|
119
125
|
const setCookie = ctx.context.responseHeaders?.get("set-cookie");
|
|
@@ -147,7 +153,15 @@ const anonymous = (options) => {
|
|
|
147
153
|
const isSameUser = newSessionUser?.id === session.user.id;
|
|
148
154
|
const newSessionIsAnonymous = Boolean(newSessionUser?.isAnonymous);
|
|
149
155
|
if (options?.disableDeleteAnonymousUser || isSameUser || newSessionIsAnonymous) return;
|
|
150
|
-
|
|
156
|
+
try {
|
|
157
|
+
await ctx.context.internalAdapter.deleteSessions(session.user.id);
|
|
158
|
+
await ctx.context.internalAdapter.deleteUser(session.user.id);
|
|
159
|
+
} catch (error) {
|
|
160
|
+
ctx.context.logger.error("Failed to clean up anonymous user during post-link cleanup", {
|
|
161
|
+
anonymousUserId: session.user.id,
|
|
162
|
+
error
|
|
163
|
+
});
|
|
164
|
+
}
|
|
151
165
|
})
|
|
152
166
|
}] },
|
|
153
167
|
options,
|
|
@@ -30,16 +30,11 @@ const bearer = (options) => {
|
|
|
30
30
|
if (authHeader.slice(0, 7).toLowerCase() !== BEARER_SCHEME) return;
|
|
31
31
|
const token = authHeader.slice(7).trim();
|
|
32
32
|
if (!token) return;
|
|
33
|
-
let signedToken;
|
|
34
33
|
let decodedToken;
|
|
35
|
-
if (token.includes("."))
|
|
36
|
-
|
|
37
|
-
signedToken = isEncoded ? token : encodeURIComponent(token);
|
|
38
|
-
decodedToken = isEncoded ? tryDecode(token) : token;
|
|
39
|
-
} else {
|
|
34
|
+
if (token.includes(".")) decodedToken = token.includes("%") ? tryDecode(token) : token;
|
|
35
|
+
else {
|
|
40
36
|
if (options?.requireSignature) return;
|
|
41
|
-
|
|
42
|
-
decodedToken = tryDecode(signedToken);
|
|
37
|
+
decodedToken = tryDecode((await serializeSignedCookie("", token, c.context.secret)).replace("=", ""));
|
|
43
38
|
}
|
|
44
39
|
try {
|
|
45
40
|
if (!await createHMAC("SHA-256", "base64urlnopad").verify(c.context.secret, decodedToken.split(".")[0], decodedToken.split(".")[1])) return;
|
|
@@ -48,7 +43,7 @@ const bearer = (options) => {
|
|
|
48
43
|
}
|
|
49
44
|
const existingHeaders = c.request?.headers || c.headers;
|
|
50
45
|
const headers = new Headers({ ...Object.fromEntries(existingHeaders?.entries()) });
|
|
51
|
-
setRequestCookie(headers, c.context.authCookies.sessionToken.name,
|
|
46
|
+
setRequestCookie(headers, c.context.authCookies.sessionToken.name, decodedToken);
|
|
52
47
|
return { context: { headers } };
|
|
53
48
|
})
|
|
54
49
|
}],
|
|
@@ -21,12 +21,12 @@ const captcha = (options) => ({
|
|
|
21
21
|
if (pathname.endsWith("//")) pathname = pathname.slice(0, -1);
|
|
22
22
|
if (pathname.startsWith("//")) pathname = pathname.slice(1);
|
|
23
23
|
if (!pathname.startsWith("/")) pathname = "/" + pathname;
|
|
24
|
-
const
|
|
24
|
+
const exemptPaths = ["/sign-in/email-otp"].reduce((acc, curr) => {
|
|
25
25
|
if (options.endpoints?.length && options.endpoints.includes(curr)) return acc;
|
|
26
26
|
return [...acc, curr];
|
|
27
27
|
}, []);
|
|
28
28
|
if (!endpoints.some((endpoint) => {
|
|
29
|
-
return pathname.includes(endpoint) && !
|
|
29
|
+
return pathname.includes(endpoint) && !exemptPaths.some((p) => pathname.includes(p));
|
|
30
30
|
})) return;
|
|
31
31
|
if (!options.secretKey) throw new Error(INTERNAL_ERROR_CODES.MISSING_SECRET_KEY.message);
|
|
32
32
|
const captchaResponse = request.headers.get("x-captcha-response");
|
|
@@ -8,6 +8,7 @@ const DEVICE_AUTHORIZATION_ERROR_CODES = defineErrorCodes({
|
|
|
8
8
|
ACCESS_DENIED: "Access denied",
|
|
9
9
|
INVALID_USER_CODE: "Invalid user code",
|
|
10
10
|
DEVICE_CODE_ALREADY_PROCESSED: "Device code already processed",
|
|
11
|
+
DEVICE_CODE_NOT_CLAIMED: "Device code has not been claimed by a verifying session; call `GET /device` with the `user_code` while signed in before approving or denying",
|
|
11
12
|
POLLING_TOO_FREQUENTLY: "Polling too frequently",
|
|
12
13
|
USER_NOT_FOUND: "User not found",
|
|
13
14
|
FAILED_TO_CREATE_SESSION: "Failed to create session",
|
|
@@ -388,6 +388,7 @@ declare const deviceAuthorization: (options?: Partial<DeviceAuthorizationOptions
|
|
|
388
388
|
ACCESS_DENIED: _better_auth_core_utils_error_codes0.RawError<"ACCESS_DENIED">;
|
|
389
389
|
INVALID_USER_CODE: _better_auth_core_utils_error_codes0.RawError<"INVALID_USER_CODE">;
|
|
390
390
|
DEVICE_CODE_ALREADY_PROCESSED: _better_auth_core_utils_error_codes0.RawError<"DEVICE_CODE_ALREADY_PROCESSED">;
|
|
391
|
+
DEVICE_CODE_NOT_CLAIMED: _better_auth_core_utils_error_codes0.RawError<"DEVICE_CODE_NOT_CLAIMED">;
|
|
391
392
|
POLLING_TOO_FREQUENTLY: _better_auth_core_utils_error_codes0.RawError<"POLLING_TOO_FREQUENTLY">;
|
|
392
393
|
INVALID_DEVICE_CODE_STATUS: _better_auth_core_utils_error_codes0.RawError<"INVALID_DEVICE_CODE_STATUS">;
|
|
393
394
|
AUTHENTICATION_REQUIRED: _better_auth_core_utils_error_codes0.RawError<"AUTHENTICATION_REQUIRED">;
|
|
@@ -99,6 +99,7 @@ Follow [rfc8628#section-3.2](https://datatracker.ietf.org/doc/html/rfc8628#secti
|
|
|
99
99
|
data: {
|
|
100
100
|
deviceCode,
|
|
101
101
|
userCode,
|
|
102
|
+
userId: null,
|
|
102
103
|
expiresAt,
|
|
103
104
|
status: "pending",
|
|
104
105
|
pollingInterval: ms(opts.interval),
|
|
@@ -331,6 +332,28 @@ const deviceVerify = createAuthEndpoint("/device", {
|
|
|
331
332
|
error: "expired_token",
|
|
332
333
|
error_description: DEVICE_AUTHORIZATION_ERROR_CODES.EXPIRED_USER_CODE.message
|
|
333
334
|
});
|
|
335
|
+
const session = await getSessionFromCtx(ctx);
|
|
336
|
+
if (session?.user?.id && !deviceCodeRecord.userId && deviceCodeRecord.status === "pending") {
|
|
337
|
+
if (await ctx.context.adapter.update({
|
|
338
|
+
model: "deviceCode",
|
|
339
|
+
where: [
|
|
340
|
+
{
|
|
341
|
+
field: "id",
|
|
342
|
+
value: deviceCodeRecord.id
|
|
343
|
+
},
|
|
344
|
+
{
|
|
345
|
+
field: "status",
|
|
346
|
+
value: "pending"
|
|
347
|
+
},
|
|
348
|
+
{
|
|
349
|
+
field: "userId",
|
|
350
|
+
operator: "eq",
|
|
351
|
+
value: null
|
|
352
|
+
}
|
|
353
|
+
],
|
|
354
|
+
update: { userId: session.user.id }
|
|
355
|
+
})) deviceCodeRecord.userId = session.user.id;
|
|
356
|
+
}
|
|
334
357
|
return ctx.json({
|
|
335
358
|
user_code,
|
|
336
359
|
status: deviceCodeRecord.status
|
|
@@ -387,7 +410,11 @@ const deviceApprove = createAuthEndpoint("/device/approve", {
|
|
|
387
410
|
error: "invalid_request",
|
|
388
411
|
error_description: DEVICE_AUTHORIZATION_ERROR_CODES.DEVICE_CODE_ALREADY_PROCESSED.message
|
|
389
412
|
});
|
|
390
|
-
if (deviceCodeRecord.userId
|
|
413
|
+
if (!deviceCodeRecord.userId) throw new APIError("BAD_REQUEST", {
|
|
414
|
+
error: "invalid_request",
|
|
415
|
+
error_description: DEVICE_AUTHORIZATION_ERROR_CODES.DEVICE_CODE_NOT_CLAIMED.message
|
|
416
|
+
});
|
|
417
|
+
if (deviceCodeRecord.userId !== session.user.id) throw new APIError("FORBIDDEN", {
|
|
391
418
|
error: "access_denied",
|
|
392
419
|
error_description: "You are not authorized to approve this device authorization"
|
|
393
420
|
});
|
|
@@ -454,7 +481,11 @@ const deviceDeny = createAuthEndpoint("/device/deny", {
|
|
|
454
481
|
error: "invalid_request",
|
|
455
482
|
error_description: DEVICE_AUTHORIZATION_ERROR_CODES.DEVICE_CODE_ALREADY_PROCESSED.message
|
|
456
483
|
});
|
|
457
|
-
if (deviceCodeRecord.userId
|
|
484
|
+
if (!deviceCodeRecord.userId) throw new APIError("BAD_REQUEST", {
|
|
485
|
+
error: "invalid_request",
|
|
486
|
+
error_description: DEVICE_AUTHORIZATION_ERROR_CODES.DEVICE_CODE_NOT_CLAIMED.message
|
|
487
|
+
});
|
|
488
|
+
if (deviceCodeRecord.userId !== session.user.id) throw new APIError("FORBIDDEN", {
|
|
458
489
|
error: "access_denied",
|
|
459
490
|
error_description: "You are not authorized to deny this device authorization"
|
|
460
491
|
});
|
|
@@ -466,7 +497,7 @@ const deviceDeny = createAuthEndpoint("/device/deny", {
|
|
|
466
497
|
}],
|
|
467
498
|
update: {
|
|
468
499
|
status: "denied",
|
|
469
|
-
userId:
|
|
500
|
+
userId: session.user.id
|
|
470
501
|
}
|
|
471
502
|
});
|
|
472
503
|
return ctx.json({ success: true });
|