@protobase/server 0.46.0 → 0.46.2

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`), so a disconnected client or a secured account ends its tokens at once. With `jwksUrl` instead of
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 on every request: its user exists and is not banned, its session
37
- * is not signed out or expired, and each role it carries is one the user still holds there. A token that fails is a
38
- * 401, so the client asks its session for a new one, which carries the roles as they are now or is refused. Answers the
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 checkTokenStanding = async (context, claims, organizations, now = /* @__PURE__ */ new Date()) => {
42
- const { adapter } = context;
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
- /** Deletes the user with their sessions and accounts, and what `auth.userDeletion` removes; refuses to delete the last active admin. */
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
- /** Deletes the user with their sessions and accounts, and what `auth.userDeletion` removes; refuses to delete the last active admin. */
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
@@ -1,4 +1,4 @@
1
1
  //#region package.json
2
- var version = "0.46.0";
2
+ var version = "0.46.2";
3
3
  //#endregion
4
4
  export { version };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@protobase/server",
3
- "version": "0.46.0",
3
+ "version": "0.46.2",
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.0",
40
- "@protobase/query": "0.46.0",
41
- "@protobase/schema": "0.46.0",
39
+ "@protobase/layout": "0.46.2",
40
+ "@protobase/query": "0.46.2",
41
+ "@protobase/schema": "0.46.2",
42
42
  "@react-email/render": "^2.1.0",
43
43
  "@scalar/hono-api-reference": "^0.12.10",
44
44
  "@sentry/core": "^11.7.0",