@clioplaylists/clio 0.1.7 → 0.1.9

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 (129) hide show
  1. package/dist/api/com/clioplaylists/alpha/feed/getRecommendedPlaylistsByUser.d.ts +3 -0
  2. package/dist/api/com/clioplaylists/alpha/feed/getRecommendedPlaylistsByUser.js +70 -0
  3. package/dist/api/health.js +10 -3
  4. package/dist/api/index.d.ts +1 -0
  5. package/dist/api/index.js +40 -2
  6. package/dist/api/oauth.d.ts +3 -0
  7. package/dist/api/oauth.js +63 -0
  8. package/dist/api/util.d.ts +1 -0
  9. package/dist/api/util.js +11 -6
  10. package/dist/api/well-known.d.ts +3 -0
  11. package/dist/api/well-known.js +35 -0
  12. package/dist/auth-verifier.d.ts +17 -4
  13. package/dist/auth-verifier.js +171 -165
  14. package/dist/client.js +15 -8
  15. package/dist/config.d.ts +23 -0
  16. package/dist/config.js +87 -7
  17. package/dist/context.d.ts +14 -0
  18. package/dist/context.js +17 -1
  19. package/dist/dataplane/client/hosts.d.ts +21 -0
  20. package/dist/dataplane/client/hosts.js +29 -0
  21. package/dist/dataplane/client/index.d.ts +13 -0
  22. package/dist/dataplane/client/index.js +120 -0
  23. package/dist/dataplane/client/util.d.ts +20 -0
  24. package/dist/dataplane/client/util.js +92 -0
  25. package/dist/dataplane/index.js +18 -2
  26. package/dist/dataplane/server/background.d.ts +1 -1
  27. package/dist/dataplane/server/background.js +12 -5
  28. package/dist/dataplane/server/db/database-schema.d.ts +5 -1
  29. package/dist/dataplane/server/db/database-schema.js +2 -1
  30. package/dist/dataplane/server/db/db.js +60 -20
  31. package/dist/dataplane/server/db/index.js +17 -1
  32. package/dist/dataplane/server/db/migrations/20250515T045948368Z-init.d.ts +3 -0
  33. package/dist/dataplane/server/db/migrations/20250515T045948368Z-init.js +170 -0
  34. package/dist/dataplane/server/db/migrations/20260119T210000000Z-song-recommendation.d.ts +3 -0
  35. package/dist/dataplane/server/db/migrations/20260119T210000000Z-song-recommendation.js +36 -0
  36. package/dist/dataplane/server/db/migrations/20260119T220000000Z-oauth.d.ts +3 -0
  37. package/dist/dataplane/server/db/migrations/20260119T220000000Z-oauth.js +25 -0
  38. package/dist/dataplane/server/db/migrations/index.d.ts +3 -2
  39. package/dist/dataplane/server/db/migrations/index.js +39 -2
  40. package/dist/dataplane/server/db/migrations/provider.js +5 -1
  41. package/dist/dataplane/server/db/pagination.d.ts +1 -1
  42. package/dist/dataplane/server/db/pagination.js +38 -25
  43. package/dist/dataplane/server/db/tables/actor-sync.d.ts +2 -2
  44. package/dist/dataplane/server/db/tables/actor-sync.js +4 -1
  45. package/dist/dataplane/server/db/tables/actor.d.ts +3 -3
  46. package/dist/dataplane/server/db/tables/actor.js +4 -1
  47. package/dist/dataplane/server/db/tables/artist-list-item.d.ts +2 -2
  48. package/dist/dataplane/server/db/tables/artist-list-item.js +4 -1
  49. package/dist/dataplane/server/db/tables/artist.js +4 -1
  50. package/dist/dataplane/server/db/tables/oauth-session.d.ts +9 -0
  51. package/dist/dataplane/server/db/tables/oauth-session.js +4 -0
  52. package/dist/dataplane/server/db/tables/oauth-state.d.ts +10 -0
  53. package/dist/dataplane/server/db/tables/oauth-state.js +4 -0
  54. package/dist/dataplane/server/db/tables/playlist-idea.d.ts +3 -3
  55. package/dist/dataplane/server/db/tables/playlist-idea.js +4 -1
  56. package/dist/dataplane/server/db/tables/playlist-item.js +4 -1
  57. package/dist/dataplane/server/db/tables/playlist.d.ts +1 -0
  58. package/dist/dataplane/server/db/tables/playlist.js +4 -1
  59. package/dist/dataplane/server/db/tables/profile.d.ts +5 -5
  60. package/dist/dataplane/server/db/tables/profile.js +4 -1
  61. package/dist/dataplane/server/db/tables/record.d.ts +1 -1
  62. package/dist/dataplane/server/db/tables/record.js +4 -1
  63. package/dist/dataplane/server/db/tables/song-recommendation.d.ts +17 -0
  64. package/dist/dataplane/server/db/tables/song-recommendation.js +4 -0
  65. package/dist/dataplane/server/db/tables/song.d.ts +2 -2
  66. package/dist/dataplane/server/db/tables/song.js +4 -1
  67. package/dist/dataplane/server/db/tables/subscription-cursor.d.ts +9 -0
  68. package/dist/dataplane/server/db/tables/subscription-cursor.js +4 -0
  69. package/dist/dataplane/server/db/types.js +2 -1
  70. package/dist/dataplane/server/db/util.d.ts +7 -3
  71. package/dist/dataplane/server/db/util.js +26 -18
  72. package/dist/dataplane/server/index.js +21 -13
  73. package/dist/dataplane/server/indexing/index.d.ts +2 -0
  74. package/dist/dataplane/server/indexing/index.js +82 -66
  75. package/dist/dataplane/server/indexing/plugins/playlist-idea.d.ts +1 -2
  76. package/dist/dataplane/server/indexing/plugins/playlist-idea.js +50 -41
  77. package/dist/dataplane/server/indexing/plugins/profile.js +45 -12
  78. package/dist/dataplane/server/indexing/plugins/song-recommendation.d.ts +9 -0
  79. package/dist/dataplane/server/indexing/plugins/song-recommendation.js +101 -0
  80. package/dist/dataplane/server/indexing/processor.js +12 -11
  81. package/dist/dataplane/server/routes/identity.d.ts +19 -0
  82. package/dist/dataplane/server/routes/identity.js +32 -25
  83. package/dist/dataplane/server/routes/index.js +15 -10
  84. package/dist/dataplane/server/routes/profile.js +17 -25
  85. package/dist/dataplane/server/routes/records.d.ts +18 -0
  86. package/dist/dataplane/server/routes/records.js +48 -22
  87. package/dist/dataplane/server/routes/sync.js +5 -3
  88. package/dist/dataplane/server/storage/subscription-cursor.d.ts +3 -0
  89. package/dist/dataplane/server/storage/subscription-cursor.js +25 -0
  90. package/dist/dataplane/server/subscription.d.ts +6 -3
  91. package/dist/dataplane/server/subscription.js +73 -63
  92. package/dist/error.js +9 -5
  93. package/dist/index.d.ts +8 -3
  94. package/dist/index.js +96 -27
  95. package/dist/lexicons/index.d.ts +3 -210
  96. package/dist/lexicons/index.js +26 -403
  97. package/dist/lexicons/lexicons.d.ts +409 -8107
  98. package/dist/lexicons/lexicons.js +134 -4276
  99. package/dist/lexicons/types/com/atproto/repo/strongRef.d.ts +4 -4
  100. package/dist/lexicons/types/com/atproto/repo/strongRef.js +13 -9
  101. package/dist/lexicons/types/com/clioplaylists/alpha/actor/profile.d.ts +10 -8
  102. package/dist/lexicons/types/com/clioplaylists/alpha/actor/profile.js +13 -9
  103. package/dist/lexicons/types/com/clioplaylists/alpha/feed/defs.d.ts +20 -11
  104. package/dist/lexicons/types/com/clioplaylists/alpha/feed/defs.js +29 -14
  105. package/dist/lexicons/types/com/clioplaylists/alpha/feed/getRecommendedPlaylistsByUser.d.ts +36 -0
  106. package/dist/lexicons/types/com/clioplaylists/alpha/feed/getRecommendedPlaylistsByUser.js +6 -0
  107. package/dist/lexicons/types/com/clioplaylists/alpha/feed/playlistIdea.d.ts +11 -14
  108. package/dist/lexicons/types/com/clioplaylists/alpha/feed/playlistIdea.js +20 -23
  109. package/dist/lexicons/types/com/clioplaylists/alpha/feed/recommendedPlaylist.d.ts +18 -0
  110. package/dist/lexicons/types/com/clioplaylists/alpha/feed/recommendedPlaylist.js +15 -0
  111. package/dist/lexicons/types/com/clioplaylists/alpha/feed/songRecommendation.d.ts +18 -0
  112. package/dist/lexicons/types/com/clioplaylists/alpha/feed/songRecommendation.js +15 -0
  113. package/dist/lexicons/util.d.ts +33 -2
  114. package/dist/lexicons/util.js +32 -4
  115. package/dist/logger.js +16 -10
  116. package/dist/oauth/client.d.ts +14 -0
  117. package/dist/oauth/client.js +126 -0
  118. package/dist/oauth/pds-agent.d.ts +3 -0
  119. package/dist/oauth/pds-agent.js +15 -0
  120. package/dist/rpc/clio_connect.d.ts +101 -11
  121. package/dist/rpc/clio_connect.js +138 -45
  122. package/dist/rpc/clio_pb.d.ts +448 -30
  123. package/dist/rpc/clio_pb.js +967 -272
  124. package/dist/start.js +9 -1
  125. package/dist/util/retry.js +10 -9
  126. package/dist/util/uris.js +6 -3
  127. package/dist/util.d.ts +0 -1
  128. package/dist/util.js +61 -20
  129. package/package.json +26 -5
@@ -1,22 +1,58 @@
1
- import { AuthRequiredError, cryptoVerifySignatureWithKey, } from '@atproto/xrpc-server';
2
- import * as jose from 'jose';
3
- import KeyEncoder from 'key-encoder';
4
- import crypto from 'node:crypto';
5
- import * as ui8 from 'uint8arrays';
6
- // import { GetIdentityByDidResponse } from './proto/bsky_pb'
7
- import { parseDidKey, SECP256K1_JWT_ALG } from '@atproto/crypto';
8
- export var RoleStatus;
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.verifySignatureWithKey = exports.createPublicKeyObject = exports.buildBasicAuth = exports.parseBasicAuth = exports.AuthVerifier = exports.RoleStatus = void 0;
40
+ const crypto_1 = require("@atproto/crypto");
41
+ const xrpc_server_1 = require("@atproto/xrpc-server");
42
+ const connect_1 = require("@connectrpc/connect");
43
+ const jose = __importStar(require("jose"));
44
+ const key_encoder_1 = __importDefault(require("key-encoder"));
45
+ const node_crypto_1 = __importDefault(require("node:crypto"));
46
+ const ui8 = __importStar(require("uint8arrays"));
47
+ const dataplane_1 = require("./dataplane");
48
+ var RoleStatus;
9
49
  (function (RoleStatus) {
10
50
  RoleStatus[RoleStatus["Valid"] = 0] = "Valid";
11
51
  RoleStatus[RoleStatus["Invalid"] = 1] = "Invalid";
12
52
  RoleStatus[RoleStatus["Missing"] = 2] = "Missing";
13
- })(RoleStatus || (RoleStatus = {}));
14
- const ALLOWED_AUTH_SCOPES = new Set([
15
- 'com.atproto.access',
16
- 'com.atproto.appPass',
17
- 'com.atproto.appPassPrivileged',
18
- ]);
19
- export class AuthVerifier {
53
+ })(RoleStatus || (exports.RoleStatus = RoleStatus = {}));
54
+ const ALLOWED_AUTH_SCOPES = new Set(['com.atproto.access', 'com.atproto.appPass', 'com.atproto.appPassPrivileged']);
55
+ class AuthVerifier {
20
56
  dataplane;
21
57
  ownDid;
22
58
  standardAudienceDids;
@@ -26,10 +62,7 @@ export class AuthVerifier {
26
62
  constructor(dataplane, opts) {
27
63
  this.dataplane = dataplane;
28
64
  this.ownDid = opts.ownDid;
29
- this.standardAudienceDids = new Set([
30
- opts.ownDid,
31
- ...opts.alternateAudienceDids,
32
- ]);
65
+ this.standardAudienceDids = new Set([opts.ownDid, ...opts.alternateAudienceDids]);
33
66
  this.modServiceDid = opts.modServiceDid;
34
67
  this.adminPasses = new Set(opts.adminPasses);
35
68
  this.entrywayJwtPublicKey = opts.entrywayJwtPublicKey;
@@ -39,12 +72,12 @@ export class AuthVerifier {
39
72
  // @TODO remove! basic auth + did supported just for testing.
40
73
  if (isBasicToken(ctx.req)) {
41
74
  const aud = this.ownDid;
42
- const iss = ctx.req.headers['appview-as-did'];
75
+ const iss = header(ctx.req, 'appview-as-did');
43
76
  if (typeof iss !== 'string' || !iss.startsWith('did:')) {
44
- throw new AuthRequiredError('bad issuer');
77
+ throw new xrpc_server_1.AuthRequiredError('bad issuer');
45
78
  }
46
79
  if (!this.parseRoleCreds(ctx.req).admin) {
47
- throw new AuthRequiredError('bad credentials');
80
+ throw new xrpc_server_1.AuthRequiredError('bad credentials');
48
81
  }
49
82
  return {
50
83
  credentials: { type: 'standard', iss, aud },
@@ -57,27 +90,23 @@ export class AuthVerifier {
57
90
  if (header?.typ === 'at+jwt') {
58
91
  // we should never use entryway session tokens in the case of flexible auth audiences (namely in the case of getFeed)
59
92
  if (opts.skipAudCheck) {
60
- throw new AuthRequiredError('Malformed token', 'InvalidToken');
93
+ throw new xrpc_server_1.AuthRequiredError('Malformed token', 'InvalidToken');
61
94
  }
62
95
  return this.entrywaySession(ctx);
63
96
  }
64
- // const { iss, aud } = await this.verifyServiceJwt(ctx, {
65
- // lxmCheck: opts.lxmCheck,
66
- // iss: null,
67
- // aud: null,
68
- // })
69
- const { aud } = {
70
- // iss: '',
71
- aud: '',
72
- };
97
+ const { iss, aud } = await this.verifyServiceJwt(ctx, {
98
+ lxmCheck: opts.lxmCheck,
99
+ iss: null,
100
+ aud: null,
101
+ });
73
102
  if (!opts.skipAudCheck && !this.standardAudienceDids.has(aud)) {
74
- throw new AuthRequiredError('jwt audience does not match service did', 'BadJwtAudience');
103
+ throw new xrpc_server_1.AuthRequiredError('jwt audience does not match service did', 'BadJwtAudience');
75
104
  }
76
105
  return {
77
106
  credentials: {
78
107
  type: 'standard',
79
- iss: '',
80
- aud: '',
108
+ iss: iss.split('#')[0] ?? iss,
109
+ aud,
81
110
  },
82
111
  };
83
112
  }
@@ -89,14 +118,14 @@ export class AuthVerifier {
89
118
  standard = async (ctx) => {
90
119
  const output = await this.standardOptional(ctx);
91
120
  if (output.credentials.type === 'none') {
92
- throw new AuthRequiredError(undefined, 'AuthMissing');
121
+ throw new xrpc_server_1.AuthRequiredError(undefined, 'AuthMissing');
93
122
  }
94
123
  return output;
95
124
  };
96
125
  role = (ctx) => {
97
126
  const creds = this.parseRoleCreds(ctx.req);
98
127
  if (creds.status !== RoleStatus.Valid) {
99
- throw new AuthRequiredError();
128
+ throw new xrpc_server_1.AuthRequiredError();
100
129
  }
101
130
  return {
102
131
  credentials: {
@@ -131,7 +160,7 @@ export class AuthVerifier {
131
160
  return this.nullCreds();
132
161
  }
133
162
  else {
134
- throw new AuthRequiredError();
163
+ throw new xrpc_server_1.AuthRequiredError();
135
164
  }
136
165
  }
137
166
  };
@@ -141,31 +170,27 @@ export class AuthVerifier {
141
170
  entrywaySession = async (reqCtx) => {
142
171
  const token = bearerTokenFromReq(reqCtx.req);
143
172
  if (!token) {
144
- throw new AuthRequiredError(undefined, 'AuthMissing');
173
+ throw new xrpc_server_1.AuthRequiredError(undefined, 'AuthMissing');
145
174
  }
146
175
  // if entryway jwt key not configured then do not parsed these tokens
147
176
  if (!this.entrywayJwtPublicKey) {
148
- throw new AuthRequiredError('Malformed token', 'InvalidToken');
177
+ throw new xrpc_server_1.AuthRequiredError('Malformed token', 'InvalidToken');
149
178
  }
150
- const res = await jose
151
- .jwtVerify(token, this.entrywayJwtPublicKey)
152
- .catch((err) => {
179
+ const res = await jose.jwtVerify(token, this.entrywayJwtPublicKey).catch((err) => {
153
180
  if (err?.['code'] === 'ERR_JWT_EXPIRED') {
154
- throw new AuthRequiredError('Token has expired', 'ExpiredToken');
181
+ throw new xrpc_server_1.AuthRequiredError('Token has expired', 'ExpiredToken');
155
182
  }
156
- throw new AuthRequiredError('Token could not be verified', 'InvalidToken');
183
+ throw new xrpc_server_1.AuthRequiredError('Token could not be verified', 'InvalidToken');
157
184
  });
158
185
  const { sub, aud, scope } = res.payload;
159
186
  if (typeof sub !== 'string' || !sub.startsWith('did:')) {
160
- throw new AuthRequiredError('Malformed token', 'InvalidToken');
187
+ throw new xrpc_server_1.AuthRequiredError('Malformed token', 'InvalidToken');
161
188
  }
162
- else if (typeof aud !== 'string' ||
163
- !aud.startsWith('did:web:') ||
164
- !aud.endsWith('.bsky.network')) {
165
- throw new AuthRequiredError('Bad token aud', 'InvalidToken');
189
+ else if (typeof aud !== 'string' || !aud.startsWith('did:web:') || !aud.endsWith('.bsky.network')) {
190
+ throw new xrpc_server_1.AuthRequiredError('Bad token aud', 'InvalidToken');
166
191
  }
167
192
  else if (typeof scope !== 'string' || !ALLOWED_AUTH_SCOPES.has(scope)) {
168
- throw new AuthRequiredError('Bad token scope', 'InvalidToken');
193
+ throw new xrpc_server_1.AuthRequiredError('Bad token scope', 'InvalidToken');
169
194
  }
170
195
  return {
171
196
  credentials: {
@@ -175,24 +200,23 @@ export class AuthVerifier {
175
200
  },
176
201
  };
177
202
  };
178
- // modService = async (reqCtx: ReqCtx): Promise<ModServiceOutput> => {
179
- // const { iss, aud } = await this.verifyServiceJwt(reqCtx, {
180
- // aud: this.ownDid,
181
- // iss: [this.modServiceDid, `${this.modServiceDid}#atproto_labeler`],
182
- // })
183
- // return { credentials: { type: 'mod_service', aud, iss } }
184
- // }
185
- // roleOrModService = async (
186
- // reqCtx: ReqCtx,
187
- // ): Promise<RoleOutput | ModServiceOutput> => {
188
- // if (isBearerToken(reqCtx.req)) {
189
- // return this.modService(reqCtx)
190
- // } else {
191
- // return this.role(reqCtx)
192
- // }
193
- // }
203
+ modService = async (reqCtx) => {
204
+ const { iss, aud } = await this.verifyServiceJwt(reqCtx, {
205
+ aud: this.ownDid,
206
+ iss: [this.modServiceDid, `${this.modServiceDid}#atproto_labeler`],
207
+ });
208
+ return { credentials: { type: 'mod_service', aud, iss } };
209
+ };
210
+ roleOrModService = async (reqCtx) => {
211
+ if (isBearerToken(reqCtx.req)) {
212
+ return this.modService(reqCtx);
213
+ }
214
+ else {
215
+ return this.role(reqCtx);
216
+ }
217
+ };
194
218
  parseRoleCreds(req) {
195
- const parsed = parseBasicAuth(req.headers.authorization || '');
219
+ const parsed = (0, exports.parseBasicAuth)(header(req, 'authorization') || '');
196
220
  const { Missing, Valid, Invalid } = RoleStatus;
197
221
  if (!parsed) {
198
222
  return { status: Missing, admin: false, moderator: false, triage: false };
@@ -203,81 +227,53 @@ export class AuthVerifier {
203
227
  }
204
228
  return { status: Invalid, admin: false };
205
229
  }
206
- // async verifyServiceJwt(
207
- // reqCtx: ReqCtx,
208
- // opts: {
209
- // iss: string[] | null
210
- // aud: string | null
211
- // lxmCheck?: (method?: string) => boolean
212
- // },
213
- // ) {
214
- // const getSigningKey = async (
215
- // iss: string,
216
- // _forceRefresh: boolean, // @TODO consider propagating to dataplane
217
- // ): Promise<string> => {
218
- // if (opts.iss !== null && !opts.iss.includes(iss)) {
219
- // throw new AuthRequiredError('Untrusted issuer', 'UntrustedIss')
220
- // }
221
- // const [did, serviceId] = iss.split('#')
222
- // const keyId =
223
- // serviceId === 'atproto_labeler' ? 'atproto_label' : 'atproto'
224
- // let identity: GetIdentityByDidResponse
225
- // try {
226
- // identity = await this.dataplane.getIdentityByDid({ did })
227
- // } catch (err) {
228
- // if (isDataplaneError(err, Code.NotFound)) {
229
- // throw new AuthRequiredError('identity unknown')
230
- // }
231
- // throw err
232
- // }
233
- // const keys = unpackIdentityKeys(identity.keys)
234
- // const didKey = getKeyAsDidKey(keys, { id: keyId })
235
- // if (!didKey) {
236
- // throw new AuthRequiredError('missing or bad key')
237
- // }
238
- // return didKey
239
- // }
240
- // const assertLxmCheck = () => {
241
- // const lxm = parseReqNsid(reqCtx.req)
242
- // if (
243
- // (opts.lxmCheck && !opts.lxmCheck(payload.lxm)) ||
244
- // (!opts.lxmCheck && payload.lxm !== lxm)
245
- // ) {
246
- // throw new AuthRequiredError(
247
- // payload.lxm !== undefined
248
- // ? `bad jwt lexicon method ("lxm"). must match: ${lxm}`
249
- // : `missing jwt lexicon method ("lxm"). must match: ${lxm}`,
250
- // 'BadJwtLexiconMethod',
251
- // )
252
- // }
253
- // }
254
- // const jwtStr = bearerTokenFromReq(reqCtx.req)
255
- // if (!jwtStr) {
256
- // throw new AuthRequiredError('missing jwt', 'MissingJwt')
257
- // }
258
- // // if validating additional scopes, skip scope check in initial validation & follow up afterwards
259
- // const payload = await verifyServiceJwt(
260
- // jwtStr,
261
- // opts.aud,
262
- // null,
263
- // getSigningKey,
264
- // verifySignatureWithKey,
265
- // )
266
- // if (
267
- // !payload.iss.endsWith('#atproto_labeler') ||
268
- // payload.lxm !== undefined
269
- // ) {
270
- // // @TODO currently permissive of labelers who dont set lxm yet.
271
- // // we'll allow ozone self-hosters to upgrade before removing this condition.
272
- // assertLxmCheck()
273
- // }
274
- // return { iss: payload.iss, aud: payload.aud }
275
- // }
230
+ async verifyServiceJwt(reqCtx, opts) {
231
+ const getSigningKey = async (iss, _forceRefresh) => {
232
+ if (opts.iss !== null && !opts.iss.includes(iss)) {
233
+ throw new xrpc_server_1.AuthRequiredError('Untrusted issuer', 'UntrustedIss');
234
+ }
235
+ const [did, serviceId] = iss.split('#');
236
+ const keyId = serviceId === 'atproto_labeler' ? 'atproto_label' : 'atproto';
237
+ let identity;
238
+ try {
239
+ identity = await this.dataplane.getIdentityByDid({ did });
240
+ }
241
+ catch (err) {
242
+ if ((0, dataplane_1.isDataplaneError)(err, connect_1.Code.NotFound)) {
243
+ throw new xrpc_server_1.AuthRequiredError('identity unknown');
244
+ }
245
+ throw err;
246
+ }
247
+ const keys = (0, dataplane_1.unpackIdentityKeys)(identity.keys);
248
+ const didKey = (0, dataplane_1.getKeyAsDidKey)(keys, { id: keyId });
249
+ if (!didKey) {
250
+ throw new xrpc_server_1.AuthRequiredError('missing or bad key');
251
+ }
252
+ return didKey;
253
+ };
254
+ const assertLxmCheck = () => {
255
+ const lxm = (0, xrpc_server_1.parseReqNsid)(reqCtx.req);
256
+ if ((opts.lxmCheck && !opts.lxmCheck(payload.lxm)) || (!opts.lxmCheck && payload.lxm !== lxm)) {
257
+ throw new xrpc_server_1.AuthRequiredError(payload.lxm !== undefined
258
+ ? `bad jwt lexicon method ("lxm"). must match: ${lxm}`
259
+ : `missing jwt lexicon method ("lxm"). must match: ${lxm}`, 'BadJwtLexiconMethod');
260
+ }
261
+ };
262
+ const jwtStr = bearerTokenFromReq(reqCtx.req);
263
+ if (!jwtStr) {
264
+ throw new xrpc_server_1.AuthRequiredError('missing jwt', 'MissingJwt');
265
+ }
266
+ // if validating additional scopes, skip scope check in initial validation & follow up afterwards
267
+ const payload = await (0, xrpc_server_1.verifyJwt)(jwtStr, opts.aud, null, getSigningKey, exports.verifySignatureWithKey);
268
+ if (!payload.iss.endsWith('#atproto_labeler') || payload.lxm !== undefined) {
269
+ // @TODO currently permissive of labelers who dont set lxm yet.
270
+ // we'll allow ozone self-hosters to upgrade before removing this condition.
271
+ assertLxmCheck();
272
+ }
273
+ return { iss: payload.iss, aud: payload.aud };
274
+ }
276
275
  isModService(iss) {
277
- return [
278
- this.modServiceDid,
279
- `${this.modServiceDid}#atproto_labeler`,
280
- ].includes(iss);
276
+ return [this.modServiceDid, `${this.modServiceDid}#atproto_labeler`].includes(iss);
281
277
  }
282
278
  nullCreds() {
283
279
  return {
@@ -291,10 +287,8 @@ export class AuthVerifier {
291
287
  const viewer = creds.credentials.type === 'standard' ? creds.credentials.iss : null;
292
288
  const includeTakedownsAnd3pBlocks = (creds.credentials.type === 'role' && creds.credentials.admin) ||
293
289
  creds.credentials.type === 'mod_service' ||
294
- (creds.credentials.type === 'standard' &&
295
- this.isModService(creds.credentials.iss));
296
- const canPerformTakedown = (creds.credentials.type === 'role' && creds.credentials.admin) ||
297
- creds.credentials.type === 'mod_service';
290
+ (creds.credentials.type === 'standard' && this.isModService(creds.credentials.iss));
291
+ const canPerformTakedown = (creds.credentials.type === 'role' && creds.credentials.admin) || creds.credentials.type === 'mod_service';
298
292
  return {
299
293
  viewer,
300
294
  includeTakedowns: includeTakedownsAnd3pBlocks,
@@ -303,23 +297,32 @@ export class AuthVerifier {
303
297
  };
304
298
  }
305
299
  }
300
+ exports.AuthVerifier = AuthVerifier;
306
301
  // HELPERS
307
302
  // ---------
308
303
  const BEARER = 'Bearer ';
309
304
  const BASIC = 'Basic ';
310
305
  const isBearerToken = (req) => {
311
- return req.headers.authorization?.startsWith(BEARER) ?? false;
306
+ const authHeader = header(req, 'authorization');
307
+ return authHeader?.startsWith(BEARER) ?? false;
312
308
  };
313
309
  const isBasicToken = (req) => {
314
- return req.headers.authorization?.startsWith(BASIC) ?? false;
310
+ const authHeader = header(req, 'authorization');
311
+ return authHeader?.startsWith(BASIC) ?? false;
315
312
  };
316
313
  const bearerTokenFromReq = (req) => {
317
- const header = req.headers.authorization || '';
318
- if (!header.startsWith(BEARER))
314
+ const authHeader = header(req, 'authorization') || '';
315
+ if (!authHeader.startsWith(BEARER))
319
316
  return null;
320
- return header.slice(BEARER.length).trim();
317
+ return authHeader.slice(BEARER.length).trim();
318
+ };
319
+ const header = (req, name) => {
320
+ const v = req.headers[name.toLowerCase()];
321
+ if (Array.isArray(v))
322
+ return v[0];
323
+ return v;
321
324
  };
322
- export const parseBasicAuth = (token) => {
325
+ const parseBasicAuth = (token) => {
323
326
  if (!token.startsWith(BASIC))
324
327
  return null;
325
328
  const b64 = token.slice(BASIC.length);
@@ -335,31 +338,34 @@ export const parseBasicAuth = (token) => {
335
338
  return null;
336
339
  return { username, password };
337
340
  };
338
- export const buildBasicAuth = (username, password) => {
339
- return (BASIC +
340
- ui8.toString(ui8.fromString(`${username}:${password}`, 'utf8'), 'base64pad'));
341
+ exports.parseBasicAuth = parseBasicAuth;
342
+ const buildBasicAuth = (username, password) => {
343
+ return BASIC + ui8.toString(ui8.fromString(`${username}:${password}`, 'utf8'), 'base64pad');
341
344
  };
342
- const keyEncoder = new KeyEncoder('secp256k1');
343
- export const createPublicKeyObject = (publicKeyHex) => {
345
+ exports.buildBasicAuth = buildBasicAuth;
346
+ const keyEncoder = new key_encoder_1.default('secp256k1');
347
+ const createPublicKeyObject = (publicKeyHex) => {
344
348
  const key = keyEncoder.encodePublic(publicKeyHex, 'raw', 'pem');
345
- return crypto.createPublicKey({ format: 'pem', key });
349
+ return node_crypto_1.default.createPublicKey({ format: 'pem', key });
346
350
  };
351
+ exports.createPublicKeyObject = createPublicKeyObject;
347
352
  const verifySig = (publicKey, data, sig) => {
348
- const keyEncoder = new KeyEncoder('secp256k1');
353
+ const keyEncoder = new key_encoder_1.default('secp256k1');
349
354
  const pemKey = keyEncoder.encodePublic(ui8.toString(publicKey, 'hex'), 'raw', 'pem');
350
- const key = crypto.createPublicKey({ format: 'pem', key: pemKey });
351
- return crypto.verify('sha256', data, {
355
+ const key = node_crypto_1.default.createPublicKey({ format: 'pem', key: pemKey });
356
+ return node_crypto_1.default.verify('sha256', data, {
352
357
  key,
353
358
  dsaEncoding: 'ieee-p1363',
354
359
  }, sig);
355
360
  };
356
- export const verifySignatureWithKey = async (didKey, msgBytes, sigBytes, alg) => {
357
- if (alg === SECP256K1_JWT_ALG) {
358
- const parsed = parseDidKey(didKey);
361
+ const verifySignatureWithKey = async (didKey, msgBytes, sigBytes, alg) => {
362
+ if (alg === crypto_1.SECP256K1_JWT_ALG) {
363
+ const parsed = (0, crypto_1.parseDidKey)(didKey);
359
364
  if (alg !== parsed.jwtAlg) {
360
365
  throw new Error(`Expected key alg ${alg}, got ${parsed.jwtAlg}`);
361
366
  }
362
367
  return verifySig(parsed.keyBytes, msgBytes, sigBytes);
363
368
  }
364
- return cryptoVerifySignatureWithKey(didKey, msgBytes, sigBytes, alg);
369
+ return (0, xrpc_server_1.cryptoVerifySignatureWithKey)(didKey, msgBytes, sigBytes, alg);
365
370
  };
371
+ exports.verifySignatureWithKey = verifySignatureWithKey;
package/dist/client.js CHANGED
@@ -1,18 +1,25 @@
1
- import { createClient } from '@connectrpc/connect';
2
- import { createGrpcTransport } from '@connectrpc/connect-node';
3
- import assert from 'node:assert';
4
- import { ClioService } from './rpc/clio_connect';
5
- export const createBaseClient = (baseUrl, opts) => {
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.createBaseClient = void 0;
7
+ const connect_1 = require("@connectrpc/connect");
8
+ const connect_node_1 = require("@connectrpc/connect-node");
9
+ const node_assert_1 = __importDefault(require("node:assert"));
10
+ const clio_connect_1 = require("./rpc/clio_connect");
11
+ const createBaseClient = (baseUrl, opts) => {
6
12
  const { httpVersion = '2', rejectUnauthorized = true } = opts;
7
- const transport = createGrpcTransport({
13
+ const transport = (0, connect_node_1.createGrpcTransport)({
8
14
  baseUrl,
9
15
  acceptCompression: [],
10
16
  httpVersion,
11
17
  nodeOptions: { rejectUnauthorized },
12
18
  });
13
- assert(validateUrl(baseUrl));
14
- return createClient(ClioService, transport);
19
+ (0, node_assert_1.default)(validateUrl(baseUrl));
20
+ return (0, connect_1.createClient)(clio_connect_1.ClioService, transport);
15
21
  };
22
+ exports.createBaseClient = createBaseClient;
16
23
  const validateUrl = (urlStr) => {
17
24
  let url;
18
25
  try {
package/dist/config.d.ts CHANGED
@@ -2,9 +2,20 @@ export interface ServerConfigValues {
2
2
  version?: string;
3
3
  debugMode?: boolean;
4
4
  port?: number;
5
+ publicUrl?: string;
5
6
  serverDid: string;
6
7
  didPlcUrl: string;
7
8
  handleResolverNameservers?: string[];
9
+ dataplaneUrls: string[];
10
+ dbPostgresUrl?: string;
11
+ dbPostgresSchema?: string;
12
+ modServiceDid: string;
13
+ adminPasswords: string[];
14
+ oauthScope?: string;
15
+ oauthAllowHttp?: boolean;
16
+ proxyPreferCompressed?: boolean;
17
+ blobRateLimitBypassKey?: string;
18
+ blobRateLimitBypassHostname?: string;
8
19
  }
9
20
  export declare class ServerConfig {
10
21
  private cfg;
@@ -16,6 +27,18 @@ export declare class ServerConfig {
16
27
  get debugMode(): boolean;
17
28
  get port(): number | undefined;
18
29
  get localUrl(): string;
30
+ get publicUrl(): string;
19
31
  get serverDid(): string;
20
32
  get didPlcUrl(): string;
33
+ get dataplaneUrls(): string[];
34
+ get dbPostgresUrl(): string | undefined;
35
+ get dbPostgresSchema(): string | undefined;
36
+ get modServiceDid(): string;
37
+ get adminPasswords(): string[];
38
+ get oauthScope(): string;
39
+ get oauthAllowHttp(): boolean;
40
+ get handleResolverNameservers(): string[] | undefined;
41
+ get proxyPreferCompressed(): boolean;
42
+ get blobRateLimitBypassKey(): string | undefined;
43
+ get blobRateLimitBypassHostname(): string | undefined;
21
44
  }