matrix-nodejs-homeserver 0.3.4 → 0.3.6

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.
Files changed (55) hide show
  1. package/AUDITBOT.md +67 -0
  2. package/README.md +55 -9
  3. package/dist/api/account.js +109 -64
  4. package/dist/api/admin.js +14 -0
  5. package/dist/api/federation.js +1 -1
  6. package/dist/api/index.js +2 -0
  7. package/dist/api/misc.js +3 -3
  8. package/dist/api/oauthserver.js +620 -0
  9. package/dist/api/sync.js +1 -1
  10. package/dist/api/threepid.js +1 -1
  11. package/dist/api/uia.js +20 -8
  12. package/dist/api/user.js +6 -3
  13. package/dist/auditbot/bot.d.ts +37 -0
  14. package/dist/auditbot/bot.js +208 -0
  15. package/dist/auditbot/client.d.ts +41 -0
  16. package/dist/auditbot/client.js +156 -0
  17. package/dist/auditbot/index.d.ts +4 -0
  18. package/dist/auditbot/index.js +4 -0
  19. package/dist/auditbot/sinks.d.ts +37 -0
  20. package/dist/auditbot/sinks.js +33 -0
  21. package/dist/core/admin.d.ts +6 -0
  22. package/dist/core/admin.js +25 -0
  23. package/dist/core/auth.d.ts +33 -2
  24. package/dist/core/auth.js +84 -21
  25. package/dist/core/oauth.js +2 -1
  26. package/dist/core/oauthserver.d.ts +141 -0
  27. package/dist/core/oauthserver.js +603 -0
  28. package/dist/core/ratelimit.d.ts +1 -1
  29. package/dist/core/ratelimit.js +4 -0
  30. package/dist/core/sso.d.ts +51 -15
  31. package/dist/core/sso.js +86 -26
  32. package/dist/homeserver.d.ts +20 -9
  33. package/dist/homeserver.js +17 -2
  34. package/dist/index.d.ts +15 -14
  35. package/dist/index.js +2 -1
  36. package/dist/storage/schema.js +20 -2
  37. package/dist/vendor/zod/v4/classic/errors.js +1 -1
  38. package/dist/vendor/zod/v4/classic/parse.js +1 -1
  39. package/dist/vendor/zod/v4/classic/schemas.js +1 -1
  40. package/dist/vendor/zod/v4/core/api.js +1 -1
  41. package/dist/vendor/zod/v4/core/checks.js +1 -1
  42. package/dist/vendor/zod/v4/core/core.js +1 -1
  43. package/dist/vendor/zod/v4/core/doc.js +1 -1
  44. package/dist/vendor/zod/v4/core/errors.js +1 -1
  45. package/dist/vendor/zod/v4/core/json-schema-processors.js +1 -1
  46. package/dist/vendor/zod/v4/core/memoizer.js +1 -1
  47. package/dist/vendor/zod/v4/core/parse.js +1 -1
  48. package/dist/vendor/zod/v4/core/regexes.js +1 -1
  49. package/dist/vendor/zod/v4/core/registries.js +1 -1
  50. package/dist/vendor/zod/v4/core/schemas.js +1 -1
  51. package/dist/vendor/zod/v4/core/to-json-schema.js +1 -1
  52. package/dist/vendor/zod/v4/core/util.js +1 -1
  53. package/dist/vendor/zod/v4/core/versions.js +1 -1
  54. package/dist/vendor/zod/v4/locales/en.js +1 -1
  55. package/package.json +28 -17
@@ -1,15 +1,27 @@
1
1
  import { Homeserver } from "../homeserver.js";
2
2
  //#region src/core/sso.d.ts
3
- /** An upstream OpenID Connect provider for `m.login.sso`. */
3
+ /**
4
+ * An upstream identity provider: OpenID Connect (found through `issuer`), or plain OAuth 2.0 with
5
+ * a user info endpoint (`endpoints`, e.g. GitHub; see `githubProvider`).
6
+ */
4
7
  export interface SsoProvider {
5
8
  /** Stable identifier, used in URLs and stored with linked accounts. */
6
9
  id: string;
7
10
  name: string;
8
- issuer: string;
11
+ /** OpenID Connect issuer; its discovery document gives the endpoints. Required without `endpoints`. */
12
+ issuer?: string;
13
+ /** Endpoints of a provider without OpenID Connect discovery. */
14
+ endpoints?: {
15
+ authorization: string;
16
+ token: string;
17
+ userinfo?: string;
18
+ };
9
19
  clientId: string;
10
20
  clientSecret?: string;
11
- /** Default: ["openid", "profile", "email"]. */
21
+ /** Default: ["openid", "profile", "email"] with `issuer`, none with `endpoints`. */
12
22
  scopes?: string[];
23
+ /** Claim that identifies the user at the provider. Default: "sub". */
24
+ subjectClaim?: string;
13
25
  icon?: string;
14
26
  brand?: string;
15
27
  /**
@@ -35,26 +47,50 @@ export interface SsoOptions {
35
47
  */
36
48
  clientAllowlist?: string[];
37
49
  }
38
- /** Where the browser goes after login, and whether the user has to confirm that client first. */
39
- export interface SsoResult {
40
- url: string;
41
- confirm: boolean;
42
- /** The client as shown to the user: its host, or its scheme for app links. */
43
- client: string;
44
- }
45
- /** Upstream OIDC login (authorization code + PKCE) issuing Matrix login tokens. */
50
+ /**
51
+ * What a login through a provider is for: a client of the legacy API (`m.login.sso`, which gets a
52
+ * login token at `redirectUrl`), or the server's own web pages (OAuth 2.0 API), which continue
53
+ * at `next` in the browser session `sid`.
54
+ */
55
+ export type SsoTarget = {
56
+ kind: "client";
57
+ redirectUrl: string;
58
+ } | {
59
+ kind: "web";
60
+ next: string;
61
+ sid: string;
62
+ };
63
+ /**
64
+ * GitHub as an identity provider (an OAuth app or GitHub app; its callback URL is
65
+ * `{publicBaseUrl}/_matrix/client/v3/login/sso/callback/github`). Users are matched by their
66
+ * numeric GitHub ID, so renaming the GitHub account keeps the Matrix account; new accounts take
67
+ * the GitHub login as their localpart.
68
+ */
69
+ export declare function githubProvider(opts: {
70
+ clientId: string;
71
+ clientSecret: string;
72
+ id?: string;
73
+ name?: string;
74
+ allowRegistration?: boolean;
75
+ mapUser?: SsoProvider["mapUser"];
76
+ }): SsoProvider;
77
+ /** Upstream login (authorization code + PKCE) for the legacy `m.login.sso` and the OAuth 2.0 API's web pages. */
46
78
  export declare class SsoService {
47
79
  private hs;
48
80
  readonly providers: SsoProvider[];
49
- private clientAllowlist;
81
+ readonly clientAllowlist: string[];
50
82
  private discovery;
51
83
  constructor(hs: Homeserver, options: SsoOptions);
52
84
  private callbackUrl;
53
85
  private discover;
86
+ private scopes;
54
87
  /** URL to send the user's browser to. */
55
- startLogin(providerId: string | null, redirectUrl: string): Promise<string>;
56
- /** Handle the provider's redirect; returns the client URL carrying a login token. */
57
- finishLogin(providerId: string, params: URLSearchParams): Promise<SsoResult>;
88
+ startLogin(providerId: string | null, target: SsoTarget): Promise<string>;
89
+ /** Handle the provider's redirect: the user who logged in, and what the login was for. */
90
+ finishLogin(providerId: string, params: URLSearchParams): Promise<{
91
+ userId: string;
92
+ target: SsoTarget;
93
+ }>;
58
94
  private resolveUser;
59
95
  }
60
96
  //#endregion
package/dist/core/sso.js CHANGED
@@ -39,7 +39,35 @@ function decodeJwtPayload(jwt) {
39
39
  if (!part) throw new Error("Malformed ID token");
40
40
  return JSON.parse(Buffer.from(part, "base64url").toString("utf8"));
41
41
  }
42
- /** Upstream OIDC login (authorization code + PKCE) issuing Matrix login tokens. */
42
+ /**
43
+ * GitHub as an identity provider (an OAuth app or GitHub app; its callback URL is
44
+ * `{publicBaseUrl}/_matrix/client/v3/login/sso/callback/github`). Users are matched by their
45
+ * numeric GitHub ID, so renaming the GitHub account keeps the Matrix account; new accounts take
46
+ * the GitHub login as their localpart.
47
+ */
48
+ function githubProvider(opts) {
49
+ return {
50
+ id: opts.id ?? "github",
51
+ name: opts.name ?? "GitHub",
52
+ brand: "github",
53
+ clientId: opts.clientId,
54
+ clientSecret: opts.clientSecret,
55
+ endpoints: {
56
+ authorization: "https://github.com/login/oauth/authorize",
57
+ token: "https://github.com/login/oauth/access_token",
58
+ userinfo: "https://api.github.com/user"
59
+ },
60
+ scopes: ["read:user"],
61
+ subjectClaim: "id",
62
+ mapUser: opts.mapUser ?? ((c) => ({
63
+ localpart: sanitizeLocalpart(String(c.login ?? c.id)),
64
+ displayname: c.name ?? c.login
65
+ })),
66
+ ...opts.allowRegistration !== void 0 ? { allowRegistration: opts.allowRegistration } : {}
67
+ };
68
+ }
69
+ const USER_AGENT = "matrix-nodejs-homeserver";
70
+ /** Upstream login (authorization code + PKCE) for the legacy `m.login.sso` and the OAuth 2.0 API's web pages. */
43
71
  var SsoService = class {
44
72
  hs;
45
73
  providers;
@@ -56,8 +84,18 @@ var SsoService = class {
56
84
  return `${base.replace(/\/$/, "")}/_matrix/client/v3/login/sso/callback/${encodeURIComponent(provider.id)}`;
57
85
  }
58
86
  async discover(provider) {
87
+ if (provider.endpoints) {
88
+ const e = provider.endpoints;
89
+ return {
90
+ issuer: provider.issuer,
91
+ authorization_endpoint: e.authorization,
92
+ token_endpoint: e.token,
93
+ userinfo_endpoint: e.userinfo
94
+ };
95
+ }
59
96
  const cached = this.discovery.get(provider.id);
60
97
  if (cached) return cached;
98
+ if (!provider.issuer) throw new Error(`Identity provider ${provider.id} needs an issuer or endpoints`);
61
99
  const res = await this.hs.fetch(`${provider.issuer.replace(/\/$/, "")}/.well-known/openid-configuration`, { signal: AbortSignal.timeout(1e4) });
62
100
  if (!res.ok) throw new Error(`OIDC discovery for ${provider.id} failed with ${res.status}`);
63
101
  const meta = await res.json();
@@ -65,8 +103,15 @@ var SsoService = class {
65
103
  this.discovery.set(provider.id, meta);
66
104
  return meta;
67
105
  }
106
+ scopes(provider) {
107
+ return provider.scopes ?? (provider.endpoints ? [] : [
108
+ "openid",
109
+ "profile",
110
+ "email"
111
+ ]);
112
+ }
68
113
  /** URL to send the user's browser to. */
69
- async startLogin(providerId, redirectUrl) {
114
+ async startLogin(providerId, target) {
70
115
  const provider = providerId ? this.providers.find((p) => p.id === providerId) : this.providers[0];
71
116
  if (!provider) throw errors.notFound("Unknown identity provider");
72
117
  const meta = await this.discover(provider);
@@ -75,7 +120,7 @@ var SsoService = class {
75
120
  const nonce = randomToken(16);
76
121
  await this.hs.cluster.set(`sso:${state}`, {
77
122
  providerId: provider.id,
78
- redirectUrl,
123
+ target,
79
124
  verifier,
80
125
  nonce,
81
126
  expires: Date.now() + 6e5
@@ -84,18 +129,15 @@ var SsoService = class {
84
129
  url.searchParams.set("response_type", "code");
85
130
  url.searchParams.set("client_id", provider.clientId);
86
131
  url.searchParams.set("redirect_uri", this.callbackUrl(provider));
87
- url.searchParams.set("scope", (provider.scopes ?? [
88
- "openid",
89
- "profile",
90
- "email"
91
- ]).join(" "));
132
+ const scopes = this.scopes(provider);
133
+ if (scopes.length) url.searchParams.set("scope", scopes.join(" "));
92
134
  url.searchParams.set("state", state);
93
135
  url.searchParams.set("nonce", nonce);
94
136
  url.searchParams.set("code_challenge", urlSafeBase64(createHash("sha256").update(verifier).digest()));
95
137
  url.searchParams.set("code_challenge_method", "S256");
96
138
  return url.toString();
97
139
  }
98
- /** Handle the provider's redirect; returns the client URL carrying a login token. */
140
+ /** Handle the provider's redirect: the user who logged in, and what the login was for. */
99
141
  async finishLogin(providerId, params) {
100
142
  const state = params.get("state") ?? "";
101
143
  const pending = await this.hs.cluster.get(`sso:${state}`);
@@ -115,7 +157,8 @@ var SsoService = class {
115
157
  });
116
158
  const headers = {
117
159
  "content-type": "application/x-www-form-urlencoded",
118
- accept: "application/json"
160
+ accept: "application/json",
161
+ "user-agent": USER_AGENT
119
162
  };
120
163
  if (provider.clientSecret) headers.authorization = `Basic ${Buffer.from(`${encodeURIComponent(provider.clientId)}:${encodeURIComponent(provider.clientSecret)}`).toString("base64")}`;
121
164
  const res = await this.hs.fetch(meta.token_endpoint, {
@@ -126,8 +169,9 @@ var SsoService = class {
126
169
  });
127
170
  if (!res.ok) throw errors.forbidden("Code exchange failed");
128
171
  const tokens = await res.json();
172
+ if (typeof tokens.error === "string") throw errors.forbidden("Code exchange failed");
129
173
  let claims = {};
130
- if ((provider.scopes ?? ["openid"]).includes("openid") && typeof tokens.id_token !== "string") throw errors.forbidden("The provider returned no ID token");
174
+ if (this.scopes(provider).includes("openid") && typeof tokens.id_token !== "string") throw errors.forbidden("The provider returned no ID token");
131
175
  if (typeof tokens.id_token === "string") {
132
176
  claims = decodeJwtPayload(tokens.id_token);
133
177
  const aud = Array.isArray(claims.aud) ? claims.aud : [claims.aud];
@@ -137,7 +181,11 @@ var SsoService = class {
137
181
  }
138
182
  if (meta.userinfo_endpoint && typeof tokens.access_token === "string") {
139
183
  const info = await this.hs.fetch(meta.userinfo_endpoint, {
140
- headers: { authorization: `Bearer ${tokens.access_token}` },
184
+ headers: {
185
+ authorization: `Bearer ${tokens.access_token}`,
186
+ accept: "application/json",
187
+ "user-agent": USER_AGENT
188
+ },
141
189
  signal: AbortSignal.timeout(1e4)
142
190
  });
143
191
  if (info.ok) {
@@ -149,37 +197,35 @@ var SsoService = class {
149
197
  };
150
198
  }
151
199
  }
152
- if (typeof claims.sub !== "string") throw errors.forbidden("Provider did not return a subject");
153
- const userId = await this.resolveUser(provider, claims);
154
- const loginToken = await this.hs.auth.createLoginToken(userId);
200
+ const rawSubject = claims[provider.subjectClaim ?? "sub"];
201
+ const subject = typeof rawSubject === "number" ? String(rawSubject) : rawSubject;
202
+ if (typeof subject !== "string" || !subject) throw errors.forbidden("Provider did not return a subject");
203
+ const userId = await this.resolveUser(provider, subject, claims);
155
204
  await this.hs.audit.record({
156
205
  action: "sso_login",
157
206
  actor: userId,
158
207
  details: {
159
208
  idp: provider.id,
160
- subject: claims.sub
209
+ subject
161
210
  }
162
211
  });
163
- const redirect = new URL(pending.redirectUrl);
164
- redirect.searchParams.set("loginToken", loginToken);
165
212
  return {
166
- url: redirect.toString(),
167
- confirm: !this.clientAllowlist.some((entry) => clientMatches(pending.redirectUrl, entry)),
168
- client: redirect.host || redirect.protocol.replace(/:$/, "")
213
+ userId,
214
+ target: pending.target
169
215
  };
170
216
  }
171
- async resolveUser(provider, claims) {
217
+ async resolveUser(provider, subject, claims) {
172
218
  const link = await this.hs.db.findOne("sso_identities", [{
173
219
  field: "idp_id",
174
220
  value: provider.id
175
221
  }, {
176
222
  field: "subject",
177
- value: claims.sub
223
+ value: subject
178
224
  }]);
179
225
  if (link) return link.user_id;
180
226
  if (provider.allowRegistration === false) throw errors.forbidden("No account is linked to this identity");
181
227
  const mapped = provider.mapUser ? await provider.mapUser(claims) : {
182
- localpart: sanitizeLocalpart(claims.preferred_username ?? claims.sub),
228
+ localpart: sanitizeLocalpart(claims.preferred_username ?? subject),
183
229
  displayname: claims.name
184
230
  };
185
231
  if (!mapped || !LOCALPART_RE.test(mapped.localpart)) throw errors.forbidden("Could not map identity to a Matrix user");
@@ -191,11 +237,25 @@ var SsoService = class {
191
237
  const userId = await this.hs.admin.createUser(localpart, { displayname: mapped.displayname });
192
238
  await this.hs.db.create("sso_identities", {
193
239
  idp_id: provider.id,
194
- subject: claims.sub,
240
+ subject,
195
241
  user_id: userId
196
242
  });
197
243
  return userId;
198
244
  }
199
245
  };
246
+ /**
247
+ * The legacy API's last step: the client URL carrying a login token for `userId`, and whether
248
+ * the user confirms the client first (clients not on `sso.clientAllowlist`).
249
+ */
250
+ async function legacyClientRedirect(hs, userId, redirectUrl) {
251
+ const loginToken = await hs.auth.createLoginToken(userId);
252
+ const redirect = new URL(redirectUrl);
253
+ redirect.searchParams.set("loginToken", loginToken);
254
+ return {
255
+ url: redirect.toString(),
256
+ confirm: !(hs.sso?.clientAllowlist ?? []).some((entry) => clientMatches(redirectUrl, entry)),
257
+ client: redirect.host || redirect.protocol.replace(/:$/, "")
258
+ };
259
+ }
200
260
  //#endregion
201
- export { SsoService, checkRedirectUrl };
261
+ export { SsoService, checkRedirectUrl, githubProvider, legacyClientRedirect, sanitizeLocalpart };
@@ -13,19 +13,20 @@ import { Notifier } from "./core/notifier.js";
13
13
  import { RoomService } from "./core/rooms.js";
14
14
  import { TypingService } from "./core/typing.js";
15
15
  import { PushService } from "./core/push.js";
16
- import { AdminService, ServerNoticesOptions } from "./core/admin.js";
17
- import { OAuthDelegation, OAuthDelegationOptions } from "./core/oauth.js";
18
- import { SsoOptions, SsoService } from "./core/sso.js";
19
- import { FederationOptions, FederationService } from "./federation/index.js";
20
- import { DelayedEventService, DelayedEventsOptions } from "./core/delayed.js";
16
+ import { ResolvedUserAuth, UserAuthAdapter } from "./adapters/auth.js";
17
+ import { MediaAdapter, Thumbnailer } from "./adapters/media.js";
21
18
  import { RateLimitAction, RateLimitRule, RateLimiter, RateLimits } from "./core/ratelimit.js";
19
+ import { FederationOptions, FederationService } from "./federation/index.js";
22
20
  import { Router } from "./http/router.js";
23
21
  import { RtcOptions } from "./api/rtc.js";
22
+ import { DelayedEventService, DelayedEventsOptions } from "./core/delayed.js";
23
+ import { OAuthDelegation, OAuthDelegationOptions } from "./core/oauth.js";
24
+ import { OAuthServer, OAuthServerOptions } from "./core/oauthserver.js";
25
+ import { SsoOptions, SsoService } from "./core/sso.js";
24
26
  import { AuditOptions, AuditService } from "./core/audit.js";
25
- import { SettingsService } from "./core/settings.js";
26
27
  import { AdminRoomOptions, AdminRoomService } from "./core/adminroom.js";
27
- import { ResolvedUserAuth, UserAuthAdapter } from "./adapters/auth.js";
28
- import { MediaAdapter, Thumbnailer } from "./adapters/media.js";
28
+ import { AdminService, ServerNoticesOptions } from "./core/admin.js";
29
+ import { SettingsService } from "./core/settings.js";
29
30
  import { ChangeTracker } from "./core/changes.js";
30
31
  import { AddressPolicy } from "./core/outbound.js";
31
32
  import { EventEmitter } from "node:events";
@@ -112,7 +113,16 @@ export interface HomeserverOptions {
112
113
  */
113
114
  /** Delegate authentication to an external OAuth 2.0 / OIDC provider (disables password login and registration). */
114
115
  oauth?: OAuthDelegationOptions;
115
- /** Upstream OpenID Connect providers for `m.login.sso`. Requires `publicBaseUrl`. */
116
+ /**
117
+ * Serve the spec's OAuth 2.0 API: users log in on the server's own web pages, with a password
118
+ * and/or the upstream providers of `sso` (e.g. `githubProvider`). Requires `publicBaseUrl`.
119
+ * Cannot be combined with `oauth`.
120
+ */
121
+ oauthServer?: boolean | OAuthServerOptions;
122
+ /**
123
+ * Upstream identity providers (OpenID Connect, or OAuth 2.0 such as GitHub) for `m.login.sso`
124
+ * and the `oauthServer` login page. Requires `publicBaseUrl`.
125
+ */
116
126
  sso?: SsoOptions;
117
127
  /**
118
128
  * Coordination between instances. The default (`localCluster()`) supports one process;
@@ -198,6 +208,7 @@ export declare class Homeserver extends EventEmitter {
198
208
  readonly rateLimits: RateLimits;
199
209
  readonly admin: AdminService;
200
210
  readonly oauth: OAuthDelegation | null;
211
+ readonly oauthServer: OAuthServer | null;
201
212
  readonly sso: SsoService | null;
202
213
  readonly federation: FederationService;
203
214
  readonly delayedEvents: DelayedEventService;
@@ -16,6 +16,7 @@ import { TypingService } from "./core/typing.js";
16
16
  import { coalesceReads } from "./storage/batch.js";
17
17
  import { ChangeTracker } from "./core/changes.js";
18
18
  import { PushService } from "./core/push.js";
19
+ import { OAuthServer } from "./core/oauthserver.js";
19
20
  import { SsoService } from "./core/sso.js";
20
21
  import { AdminService } from "./core/admin.js";
21
22
  import { OAuthDelegation } from "./core/oauth.js";
@@ -75,6 +76,7 @@ var Homeserver = class extends EventEmitter {
75
76
  rateLimits;
76
77
  admin;
77
78
  oauth;
79
+ oauthServer;
78
80
  sso;
79
81
  federation;
80
82
  delayedEvents;
@@ -153,6 +155,9 @@ var Homeserver = class extends EventEmitter {
153
155
  this.push = new PushService(this);
154
156
  this.admin = new AdminService(this);
155
157
  this.oauth = options.oauth ? new OAuthDelegation(this, options.oauth) : null;
158
+ if (options.oauthServer && options.oauth) throw new Error("`oauthServer` and `oauth` (delegated OAuth) cannot be used together");
159
+ if (options.oauthServer && !options.publicBaseUrl) throw new Error("`publicBaseUrl` is required for `oauthServer`");
160
+ this.oauthServer = options.oauthServer ? new OAuthServer(this, options.oauthServer === true ? {} : options.oauthServer, options.publicBaseUrl) : null;
156
161
  this.fetch = options.fetch ?? globalThis.fetch;
157
162
  this.addressPolicy = addressPolicy(options.allowPrivateAddresses);
158
163
  this.publicFetch = policyFetch(this.addressPolicy, options.fetch);
@@ -167,7 +172,7 @@ var Homeserver = class extends EventEmitter {
167
172
  this.identityServers = new IdentityServers(this);
168
173
  const adminRoom = options.adminRoom === true ? {} : options.adminRoom || null;
169
174
  this.adminRoom = adminRoom && adminRoom.enabled !== false ? new AdminRoomService(this, adminRoom) : null;
170
- this.sso = options.sso?.providers.length ? new SsoService(this, options.sso) : null;
175
+ this.sso = options.sso && (options.sso.providers.length || options.oauthServer) ? new SsoService(this, options.sso) : null;
171
176
  if (this.sso && !options.publicBaseUrl) throw new Error("`publicBaseUrl` is required when SSO is configured");
172
177
  this.rateLimits = options.rateLimits === false ? new RateLimits(null) : new RateLimits(options.rateLimits?.limiter ?? memoryRateLimiter(), options.rateLimits?.rules);
173
178
  const cleanupEvery = options.cleanupIntervalMs ?? 6e5;
@@ -222,7 +227,13 @@ var Homeserver = class extends EventEmitter {
222
227
  limit: 1
223
228
  });
224
229
  if (oldestSnap[0] && oldestSnap[0].id < 0) await this.streams.lowerBackfillTo(oldestSnap[0].id);
225
- await this.cluster.advance("federation_outbox", await this.db.max("federation_outbox", "id") ?? 0);
230
+ for (const model of [
231
+ "federation_outbox",
232
+ "audit_log",
233
+ "event_reports",
234
+ "user_reports",
235
+ "appservice_outbox"
236
+ ]) await this.cluster.advance(model, await this.db.max(model, "id") ?? 0);
226
237
  if (this.federation.enabled) this.federation.sender.start();
227
238
  this.delayedEvents.start();
228
239
  this.appservices.start();
@@ -235,7 +246,11 @@ var Homeserver = class extends EventEmitter {
235
246
  field: "expires_ts",
236
247
  operator: "lt",
237
248
  value: now - 6048e5
249
+ }, {
250
+ field: "refresh_used",
251
+ value: true
238
252
  }]);
253
+ await this.oauthServer?.prune();
239
254
  await this.db.delete("login_tokens", [{
240
255
  field: "expires_ts",
241
256
  operator: "lt",
package/dist/index.d.ts CHANGED
@@ -3,24 +3,25 @@ import { M, UNSTABLE } from "./core/m.js";
3
3
  import { AppServiceRegistration, loadAppServiceRegistrations, parseAppServiceRegistration } from "./core/appservices.js";
4
4
  import { DatabaseAdapter, FieldDef, FindManyOptions, ModelDef, Operator, Row, Schema, WhereClause } from "./storage/types.js";
5
5
  import { ClusterAdapter, localCluster } from "./core/cluster.js";
6
- import { AdminService, ServerNoticesOptions } from "./core/admin.js";
7
- import { OAuthDelegationOptions } from "./core/oauth.js";
8
- import { SsoOptions, SsoProvider } from "./core/sso.js";
9
- import { MatrixError } from "./core/errors.js";
10
- import { FederationOptions } from "./federation/index.js";
11
- import { DelayedEventsOptions } from "./core/delayed.js";
12
- import { RateLimitAction, RateLimitRule, RateLimiter, memoryRateLimiter } from "./core/ratelimit.js";
13
- import { RtcOptions, TurnOptions } from "./api/rtc.js";
14
- import { AuditAction, AuditEntry, AuditOptions } from "./core/audit.js";
15
- import { AdminRoomOptions } from "./core/adminroom.js";
16
- import { PasswordCheckContext, UserAuthAdapter } from "./adapters/auth.js";
17
- import { MediaAdapter, MediaMetadata, ThumbnailRequest, Thumbnailer, filesystemMedia, memoryMedia, sharpThumbnailer } from "./adapters/media.js";
18
- import { Homeserver, HomeserverOptions, Logger, createHomeserver } from "./homeserver.js";
19
6
  import { NodeHandler, getExpressHandler, getFastifyPlugin, getFetchHandler, getHonoHandler, getNodeHandler, isMatrixPath } from "./http/adapters.js";
20
7
  import { handleRequest } from "./http/handler.js";
8
+ import { MatrixError } from "./core/errors.js";
21
9
  import { memoryAdapter } from "./storage/memory.js";
22
10
  import { MysqlPoolLike, PgPoolLike, SqlAdapterOptions, SqlDialect, SqlExecutor, SqliteDatabaseLike, mysqlAdapter, postgresAdapter, sqlAdapter, sqliteAdapter } from "./storage/sql.js";
23
11
  import { schema } from "./storage/schema.js";
12
+ import { PasswordCheckContext, UserAuthAdapter } from "./adapters/auth.js";
13
+ import { MediaAdapter, MediaMetadata, ThumbnailRequest, Thumbnailer, filesystemMedia, memoryMedia, sharpThumbnailer } from "./adapters/media.js";
24
14
  import { S3MediaOptions, s3Media } from "./adapters/s3.js";
15
+ import { RateLimitAction, RateLimitRule, RateLimiter, memoryRateLimiter } from "./core/ratelimit.js";
25
16
  import { PgClusterPoolLike, postgresCluster } from "./adapters/cluster.js";
26
- export { type AdminRoomOptions, type AdminService, type AppServiceRegistration, type AuditAction, type AuditEntry, type AuditOptions, type ClusterAdapter, type DatabaseAdapter, type DelayedEventsOptions, type FederationOptions, type FieldDef, type FindManyOptions, Homeserver, type HomeserverOptions, type Logger, M, MatrixError, type MediaAdapter, type MediaMetadata, type ModelDef, type MysqlPoolLike, type NodeHandler, type OAuthDelegationOptions, type Operator, type PasswordCheckContext, type PgClusterPoolLike, type PgPoolLike, type RateLimitAction, type RateLimitRule, type RateLimiter, type Row, type RtcOptions, type S3MediaOptions, type Schema, type ServerNoticesOptions, type SqlAdapterOptions, type SqlDialect, type SqlExecutor, type SqliteDatabaseLike, type SsoOptions, type SsoProvider, type ThreepidEmail, type ThreepidOptions, type ThreepidPurpose, type ThreepidSms, type ThumbnailRequest, type Thumbnailer, type TurnOptions, UNSTABLE, type UserAuthAdapter, type WhereClause, createHomeserver, filesystemMedia, getExpressHandler, getFastifyPlugin, getFetchHandler, getHonoHandler, getNodeHandler, handleRequest, isMatrixPath, loadAppServiceRegistrations, localCluster, memoryAdapter, memoryMedia, memoryRateLimiter, mysqlAdapter, parseAppServiceRegistration, postgresAdapter, postgresCluster, s3Media, schema, sharpThumbnailer, sqlAdapter, sqliteAdapter };
17
+ import { FederationOptions } from "./federation/index.js";
18
+ import { RtcOptions, TurnOptions } from "./api/rtc.js";
19
+ import { DelayedEventsOptions } from "./core/delayed.js";
20
+ import { OAuthDelegationOptions } from "./core/oauth.js";
21
+ import { OAuthServerOptions } from "./core/oauthserver.js";
22
+ import { SsoOptions, SsoProvider, githubProvider } from "./core/sso.js";
23
+ import { AuditAction, AuditEntry, AuditOptions } from "./core/audit.js";
24
+ import { AdminRoomOptions } from "./core/adminroom.js";
25
+ import { AdminService, ServerNoticesOptions } from "./core/admin.js";
26
+ import { Homeserver, HomeserverOptions, Logger, createHomeserver } from "./homeserver.js";
27
+ export { type AdminRoomOptions, type AdminService, type AppServiceRegistration, type AuditAction, type AuditEntry, type AuditOptions, type ClusterAdapter, type DatabaseAdapter, type DelayedEventsOptions, type FederationOptions, type FieldDef, type FindManyOptions, Homeserver, type HomeserverOptions, type Logger, M, MatrixError, type MediaAdapter, type MediaMetadata, type ModelDef, type MysqlPoolLike, type NodeHandler, type OAuthDelegationOptions, type OAuthServerOptions, type Operator, type PasswordCheckContext, type PgClusterPoolLike, type PgPoolLike, type RateLimitAction, type RateLimitRule, type RateLimiter, type Row, type RtcOptions, type S3MediaOptions, type Schema, type ServerNoticesOptions, type SqlAdapterOptions, type SqlDialect, type SqlExecutor, type SqliteDatabaseLike, type SsoOptions, type SsoProvider, type ThreepidEmail, type ThreepidOptions, type ThreepidPurpose, type ThreepidSms, type ThumbnailRequest, type Thumbnailer, type TurnOptions, UNSTABLE, type UserAuthAdapter, type WhereClause, createHomeserver, filesystemMedia, getExpressHandler, getFastifyPlugin, getFetchHandler, getHonoHandler, getNodeHandler, githubProvider, handleRequest, isMatrixPath, loadAppServiceRegistrations, localCluster, memoryAdapter, memoryMedia, memoryRateLimiter, mysqlAdapter, parseAppServiceRegistration, postgresAdapter, postgresCluster, s3Media, schema, sharpThumbnailer, sqlAdapter, sqliteAdapter };
package/dist/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { M, UNSTABLE } from "./core/m.js";
2
2
  import { MatrixError } from "./core/errors.js";
3
3
  import { localCluster } from "./core/cluster.js";
4
+ import { githubProvider } from "./core/sso.js";
4
5
  import { loadAppServiceRegistrations, parseAppServiceRegistration } from "./core/appservices.js";
5
6
  import { memoryRateLimiter } from "./core/ratelimit.js";
6
7
  import { filesystemMedia, memoryMedia, sharpThumbnailer } from "./adapters/media.js";
@@ -12,4 +13,4 @@ import { getExpressHandler, getFastifyPlugin, getFetchHandler, getHonoHandler, g
12
13
  import { mysqlAdapter, postgresAdapter, sqlAdapter, sqliteAdapter } from "./storage/sql.js";
13
14
  import { s3Media } from "./adapters/s3.js";
14
15
  import { postgresCluster } from "./adapters/cluster.js";
15
- export { Homeserver, M, MatrixError, UNSTABLE, createHomeserver, filesystemMedia, getExpressHandler, getFastifyPlugin, getFetchHandler, getHonoHandler, getNodeHandler, handleRequest, isMatrixPath, loadAppServiceRegistrations, localCluster, memoryAdapter, memoryMedia, memoryRateLimiter, mysqlAdapter, parseAppServiceRegistration, postgresAdapter, postgresCluster, s3Media, schema, sharpThumbnailer, sqlAdapter, sqliteAdapter };
16
+ export { Homeserver, M, MatrixError, UNSTABLE, createHomeserver, filesystemMedia, getExpressHandler, getFastifyPlugin, getFetchHandler, getHonoHandler, getNodeHandler, githubProvider, handleRequest, isMatrixPath, loadAppServiceRegistrations, localCluster, memoryAdapter, memoryMedia, memoryRateLimiter, mysqlAdapter, parseAppServiceRegistration, postgresAdapter, postgresCluster, s3Media, schema, sharpThumbnailer, sqlAdapter, sqliteAdapter };
@@ -48,10 +48,28 @@ const schema = {
48
48
  user_id: s,
49
49
  device_id: s,
50
50
  refresh_token: sn,
51
- expires_ts: nn
51
+ expires_ts: nn,
52
+ client_id: sn,
53
+ scope: sn,
54
+ refresh_parent: sn,
55
+ refresh_used: bn
52
56
  },
53
57
  primaryKey: ["token"],
54
- indexes: [["user_id"], ["refresh_token"]]
58
+ indexes: [
59
+ ["user_id"],
60
+ ["refresh_token"],
61
+ ["client_id"],
62
+ ["refresh_parent"]
63
+ ]
64
+ },
65
+ oauth_clients: {
66
+ fields: {
67
+ client_id: s,
68
+ metadata: j,
69
+ created_ts: n
70
+ },
71
+ primaryKey: ["client_id"],
72
+ indexes: [["created_ts"]]
55
73
  },
56
74
  rooms: {
57
75
  fields: {
@@ -1,7 +1,7 @@
1
1
  import { jsonStringifyReplacer } from "../core/util.js";
2
2
  import { $constructor } from "../core/core.js";
3
3
  import { $ZodError, flattenError, formatError } from "../core/errors.js";
4
- //#region node_modules/zod/v4/classic/errors.js
4
+ //#region ../../node_modules/zod/v4/classic/errors.js
5
5
  const _installedErrorProtos = /* @__PURE__ */ new WeakSet([Object.prototype, Error.prototype]);
6
6
  function _lazyMethod(proto, key, make) {
7
7
  Object.defineProperty(proto, key, {
@@ -1,6 +1,6 @@
1
1
  import { _decode, _decodeAsync, _encode, _encodeAsync, _parse, _parseAsync, _safeDecode, _safeDecodeAsync, _safeEncode, _safeEncodeAsync, _safeParse, _safeParseAsync } from "../core/parse.js";
2
2
  import { ZodRealError } from "./errors.js";
3
- //#region node_modules/zod/v4/classic/parse.js
3
+ //#region ../../node_modules/zod/v4/classic/parse.js
4
4
  const parse = /* @__PURE__ */ _parse(ZodRealError);
5
5
  const parseAsync = /* @__PURE__ */ _parseAsync(ZodRealError);
6
6
  const safeParse = /* @__PURE__ */ _safeParse(ZodRealError);
@@ -9,7 +9,7 @@ import { _array, _base64, _base64url, _boolean, _cidrv4, _cidrv6, _cuid, _cuid2,
9
9
  import { createStandardJSONSchemaMethod, createToJSONSchemaMethod } from "../core/to-json-schema.js";
10
10
  import { aggregateChecks, arrayProcessor, booleanProcessor, catchProcessor, customProcessor, defaultProcessor, enumProcessor, intersectionProcessor, neverProcessor, nonoptionalProcessor, nullableProcessor, numberProcessor, objectProcessor, optionalProcessor, pipeProcessor, prefaultProcessor, readonlyProcessor, recordProcessor, stringProcessor, transformProcessor, unionProcessor, unknownProcessor } from "../core/json-schema-processors.js";
11
11
  import { decode, decodeAsync, encode, encodeAsync, parse, parseAsync, safeDecode, safeDecodeAsync, safeEncode, safeEncodeAsync, safeParse, safeParseAsync } from "./parse.js";
12
- //#region node_modules/zod/v4/classic/schemas.js
12
+ //#region ../../node_modules/zod/v4/classic/schemas.js
13
13
  function _ensureDefaultLocale() {
14
14
  if (!globalConfig.localeError) config(en_default());
15
15
  }
@@ -1,6 +1,6 @@
1
1
  import { issue, normalizeParams, slugify } from "./util.js";
2
2
  import { $ZodCheck, $ZodCheckEndsWith, $ZodCheckGreaterThan, $ZodCheckIncludes, $ZodCheckLengthEquals, $ZodCheckLessThan, $ZodCheckLowerCase, $ZodCheckMaxLength, $ZodCheckMinLength, $ZodCheckMultipleOf, $ZodCheckOverwrite, $ZodCheckRegex, $ZodCheckStartsWith, $ZodCheckUpperCase } from "./checks.js";
3
- //#region node_modules/zod/v4/core/api.js
3
+ //#region ../../node_modules/zod/v4/core/api.js
4
4
  function snapshotChecks(def) {
5
5
  if (def.checks) def.checks = [...def.checks];
6
6
  return def;
@@ -1,7 +1,7 @@
1
1
  import { NUMBER_FORMAT_RANGES, codePointLength, escapeRegex, floatSafeRemainder, getLengthableOrigin, nullish } from "./util.js";
2
2
  import { $constructor } from "./core.js";
3
3
  import { lowercase, uppercase } from "./regexes.js";
4
- //#region node_modules/zod/v4/core/checks.js
4
+ //#region ../../node_modules/zod/v4/core/checks.js
5
5
  const $ZodCheck = /*@__PURE__*/ $constructor("$ZodCheck", (inst, def) => {
6
6
  var _a;
7
7
  inst._zod ?? (inst._zod = {});
@@ -1,5 +1,5 @@
1
1
  import { members } from "./util.js";
2
- //#region node_modules/zod/v4/core/core.js
2
+ //#region ../../node_modules/zod/v4/core/core.js
3
3
  var _a;
4
4
  const _zodDesc = {
5
5
  value: void 0,
@@ -1,4 +1,4 @@
1
- //#region node_modules/zod/v4/core/doc.js
1
+ //#region ../../node_modules/zod/v4/core/doc.js
2
2
  var Doc = class {
3
3
  constructor(args = [], closed = {}) {
4
4
  this.content = [];
@@ -1,6 +1,6 @@
1
1
  import { jsonStringifyReplacer } from "./util.js";
2
2
  import { $constructor } from "./core.js";
3
- //#region node_modules/zod/v4/core/errors.js
3
+ //#region ../../node_modules/zod/v4/core/errors.js
4
4
  function _getMessage() {
5
5
  const internals = this._zod;
6
6
  internals.message ?? (internals.message = JSON.stringify(internals.def, jsonStringifyReplacer, 2));
@@ -2,7 +2,7 @@ import { BIGINT_FORMAT_RANGES, NUMBER_FORMAT_RANGES, assignProp, getEnumValues }
2
2
  import { base64, base64url, integer, number } from "./regexes.js";
3
3
  import { base64Charset, base64urlCharset } from "./schemas.js";
4
4
  import { handleUnrepresentable, process as processSchema } from "./to-json-schema.js";
5
- //#region node_modules/zod/v4/core/json-schema-processors.js
5
+ //#region ../../node_modules/zod/v4/core/json-schema-processors.js
6
6
  const narrowMin = (agg, key, value) => {
7
7
  if (agg[key] === void 0 || value > agg[key]) agg[key] = value;
8
8
  };
@@ -1,5 +1,5 @@
1
1
  import { rawShape } from "./util.js";
2
- //#region node_modules/zod/v4/core/memoizer.js
2
+ //#region ../../node_modules/zod/v4/core/memoizer.js
3
3
  var $ZodCyclicError = class extends Error {
4
4
  constructor() {
5
5
  super(`Cannot parse a reference cycle that closes through a transform`);
@@ -1,6 +1,6 @@
1
1
  import { captureStackTrace, finalizeIssue } from "./util.js";
2
2
  import { $ZodAsyncError, config } from "./core.js";
3
- //#region node_modules/zod/v4/core/parse.js
3
+ //#region ../../node_modules/zod/v4/core/parse.js
4
4
  function finalizeParams(callee, params) {
5
5
  return {
6
6
  callee: params?.callee ?? callee,
@@ -1,4 +1,4 @@
1
- //#region node_modules/zod/v4/core/regexes.js
1
+ //#region ../../node_modules/zod/v4/core/regexes.js
2
2
  /**
3
3
  * @deprecated CUID v1 is deprecated by its authors due to information leakage
4
4
  * (timestamps embedded in the id). Use {@link cuid2} instead.
@@ -1,4 +1,4 @@
1
- //#region node_modules/zod/v4/core/registries.js
1
+ //#region ../../node_modules/zod/v4/core/registries.js
2
2
  var _a;
3
3
  var $ZodRegistry = class {
4
4
  constructor() {
@@ -4,7 +4,7 @@ import { anyString, boolean, cidrv4, cidrv6, cuid, cuid2, date, datetime, durati
4
4
  import { $ZodCheck, $ZodCheckNumberFormat, $ZodCheckStringFormat } from "./checks.js";
5
5
  import { Doc } from "./doc.js";
6
6
  import { version } from "./versions.js";
7
- //#region node_modules/zod/v4/core/schemas.js
7
+ //#region ../../node_modules/zod/v4/core/schemas.js
8
8
  const $ZodType = /*@__PURE__*/ $constructor("$ZodType", (inst, def) => {
9
9
  var _a;
10
10
  inst ?? (inst = {});