@protobase/server 0.46.0 → 0.46.1
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.
|
@@ -32,7 +32,9 @@ export type BetterAuthAuthenticator = Authenticator & {
|
|
|
32
32
|
* proof of the key for a DPoP-bound token, checked against `audience` as the URL it guards (the endpoint must answer
|
|
33
33
|
* at exactly that URL). The app's own tokens and trusted apps' are refused; the session keeps the token's `client`.
|
|
34
34
|
* With `auth`, every request also reads the store to check that the person still allows the client
|
|
35
|
-
* (`checkMcpGrant`),
|
|
35
|
+
* (`checkMcpGrant`), and that they exist unbanned and still hold each role the token carries where it works
|
|
36
|
+
* (`checkUserStanding`), so a disconnected client, a secured account, a ban, a lowered role or a deletion ends its
|
|
37
|
+
* tokens at once; the session's roles are the token's checked ones, and nothing later reads the claims again. With `jwksUrl` instead of
|
|
36
38
|
* `auth`, a token works until it expires, and a DPoP proof's single use holds in this process only.
|
|
37
39
|
*/
|
|
38
40
|
forAudience: (audience: string) => Authenticator;
|
|
@@ -3,7 +3,7 @@ import { noGlobalRole } from "./organizations/role-definitions.js";
|
|
|
3
3
|
import { cachedKeys } from "./jwks-cache.js";
|
|
4
4
|
import { withTrustedApps } from "./app-tokens.js";
|
|
5
5
|
import { checkMcpGrant } from "./mcp/grant-standing.js";
|
|
6
|
-
import { checkTokenStanding, tokenClaims } from "./token-standing.js";
|
|
6
|
+
import { checkTokenStanding, checkUserStanding, tokenClaims } from "./token-standing.js";
|
|
7
7
|
import { createDpopReplayStore, createInMemoryDpopReplayStore } from "better-auth/oauth2";
|
|
8
8
|
//#region src/better-auth/authenticator.ts
|
|
9
9
|
/** How the tokens of this Better Auth instance verify: its key set, and its issuer as both issuer and audience. */
|
|
@@ -75,6 +75,8 @@ const betterAuthAuthenticator = (options) => {
|
|
|
75
75
|
clientId: session.client?.id,
|
|
76
76
|
issuedAt: payload.iat
|
|
77
77
|
});
|
|
78
|
+
const { sessionId: _, ...claims } = tokenClaims(payload, session.user.roles, organizations ? session.tenant : void 0);
|
|
79
|
+
await checkUserStanding(context, claims, organizations);
|
|
78
80
|
};
|
|
79
81
|
return Object.assign(withTrustedApps(own, issuer, options.apps ?? []), { forAudience: (audience) => sessionOf({
|
|
80
82
|
audience,
|
|
@@ -33,22 +33,12 @@ const heldWhere = async ({ adapter }, user, organizations, organizationId) => {
|
|
|
33
33
|
return global.length > 0 && await findOrganization(adapter, organizationId) !== null ? global : void 0;
|
|
34
34
|
};
|
|
35
35
|
/**
|
|
36
|
-
* Whether a token still stands, read from the store
|
|
37
|
-
* is
|
|
38
|
-
*
|
|
39
|
-
* sign-in the token's session stands for (`signInOf`), `undefined` for a token without one.
|
|
36
|
+
* Whether a token's user still stands, read from the store: they exist and are not banned, and each role the token
|
|
37
|
+
* carries is one they still hold where it works. The part of `checkTokenStanding` an MCP access token gets, whose
|
|
38
|
+
* grant outlives the browser session that made it.
|
|
40
39
|
*/
|
|
41
|
-
const
|
|
42
|
-
const
|
|
43
|
-
const session = claims.sessionId === void 0 ? void 0 : await adapter.findOne({
|
|
44
|
-
model: "session",
|
|
45
|
-
where: [{
|
|
46
|
-
field: "id",
|
|
47
|
-
value: claims.sessionId
|
|
48
|
-
}]
|
|
49
|
-
});
|
|
50
|
-
if (claims.sessionId !== void 0 && (!session || session.userId !== claims.userId || session.expiresAt <= now)) throw revoked();
|
|
51
|
-
const user = await adapter.findOne({
|
|
40
|
+
const checkUserStanding = async (context, claims, organizations, now = /* @__PURE__ */ new Date()) => {
|
|
41
|
+
const user = await context.adapter.findOne({
|
|
52
42
|
model: "user",
|
|
53
43
|
where: [{
|
|
54
44
|
field: "id",
|
|
@@ -58,7 +48,24 @@ const checkTokenStanding = async (context, claims, organizations, now = /* @__PU
|
|
|
58
48
|
if (!user || bannedNow(user, now)) throw revoked();
|
|
59
49
|
const held = await heldWhere(context, user, organizations, claims.organizationId);
|
|
60
50
|
if (!held || claims.roles.some((role) => !held.includes(role))) throw revoked();
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* Whether a token still stands, read from the store on every request: its session is not signed out or expired, and
|
|
54
|
+
* its user still stands (`checkUserStanding`). A token that fails is a 401, so the client asks its session for a new
|
|
55
|
+
* one, which carries the roles as they are now or is refused. Answers the sign-in the token's session stands for
|
|
56
|
+
* (`signInOf`), `undefined` for a token without one.
|
|
57
|
+
*/
|
|
58
|
+
const checkTokenStanding = async (context, claims, organizations, now = /* @__PURE__ */ new Date()) => {
|
|
59
|
+
const session = claims.sessionId === void 0 ? void 0 : await context.adapter.findOne({
|
|
60
|
+
model: "session",
|
|
61
|
+
where: [{
|
|
62
|
+
field: "id",
|
|
63
|
+
value: claims.sessionId
|
|
64
|
+
}]
|
|
65
|
+
});
|
|
66
|
+
if (claims.sessionId !== void 0 && (!session || session.userId !== claims.userId || session.expiresAt <= now)) throw revoked();
|
|
67
|
+
await checkUserStanding(context, claims, organizations, now);
|
|
61
68
|
return session ? signInOf(session, claims.sessionId) : void 0;
|
|
62
69
|
};
|
|
63
70
|
//#endregion
|
|
64
|
-
export { checkTokenStanding, signInOf, tokenClaims };
|
|
71
|
+
export { checkTokenStanding, checkUserStanding, signInOf, tokenClaims };
|
|
@@ -42,7 +42,10 @@ export declare const createUser: (auth: AdminAuth, input: NewUser) => Promise<{
|
|
|
42
42
|
role: string;
|
|
43
43
|
}>;
|
|
44
44
|
export declare const listUsers: (auth: AdminAuth) => Promise<StoredUser[]>;
|
|
45
|
-
/**
|
|
45
|
+
/**
|
|
46
|
+
* Deletes the user with their sessions and accounts, their MCP grants with MCP on, and what `auth.userDeletion`
|
|
47
|
+
* removes; refuses to delete the last active admin.
|
|
48
|
+
*/
|
|
46
49
|
export declare const deleteUser: (auth: AdminAuth, email: string) => Promise<void>;
|
|
47
50
|
/** Sets the role: one of the configured roles, or several separated by commas; refuses to demote the last active admin. */
|
|
48
51
|
export declare const setUserRole: (auth: AdminAuth, input: {
|
|
@@ -80,13 +80,17 @@ const listUsers = async (auth) => (await allUsers(auth)).map((user) => ({
|
|
|
80
80
|
banned: Boolean(user.banned),
|
|
81
81
|
createdAt: new Date(user.createdAt).toISOString()
|
|
82
82
|
}));
|
|
83
|
-
/**
|
|
83
|
+
/**
|
|
84
|
+
* Deletes the user with their sessions and accounts, their MCP grants with MCP on, and what `auth.userDeletion`
|
|
85
|
+
* removes; refuses to delete the last active admin.
|
|
86
|
+
*/
|
|
84
87
|
const deleteUser = async (auth, email) => {
|
|
85
88
|
const user = await findUser(auth, email);
|
|
86
89
|
await refuseLastAdmin(auth, user, "delete");
|
|
87
90
|
const { internalAdapter } = await auth.$context;
|
|
88
91
|
await auth.userDeletion.run(user.id);
|
|
89
92
|
await internalAdapter.deleteUserSessions(user.id);
|
|
93
|
+
if (auth.mcp) await revokeMcpGrants((await auth.$context).adapter, user.id);
|
|
90
94
|
await internalAdapter.deleteUser(user.id);
|
|
91
95
|
await auth.userDeletion.run(user.id);
|
|
92
96
|
};
|
package/dist/package.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@protobase/server",
|
|
3
|
-
"version": "0.46.
|
|
3
|
+
"version": "0.46.1",
|
|
4
4
|
"description": "The Protobase REST API as a Hono app, with Better Auth sign-in.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -36,9 +36,9 @@
|
|
|
36
36
|
"@better-auth/passkey": "^1.7.12",
|
|
37
37
|
"@better-auth/sso": "^1.7.12",
|
|
38
38
|
"@modelcontextprotocol/server": "^2.3.1",
|
|
39
|
-
"@protobase/layout": "0.46.
|
|
40
|
-
"@protobase/query": "0.46.
|
|
41
|
-
"@protobase/schema": "0.46.
|
|
39
|
+
"@protobase/layout": "0.46.1",
|
|
40
|
+
"@protobase/query": "0.46.1",
|
|
41
|
+
"@protobase/schema": "0.46.1",
|
|
42
42
|
"@react-email/render": "^2.1.0",
|
|
43
43
|
"@scalar/hono-api-reference": "^0.12.10",
|
|
44
44
|
"@sentry/core": "^11.7.0",
|