@clioplaylists/clio 0.1.8 → 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.
- package/dist/api/com/clioplaylists/alpha/feed/getRecommendedPlaylistsByUser.d.ts +3 -0
- package/dist/api/com/clioplaylists/alpha/feed/getRecommendedPlaylistsByUser.js +70 -0
- package/dist/api/health.js +10 -3
- package/dist/api/index.d.ts +1 -0
- package/dist/api/index.js +40 -2
- package/dist/api/oauth.d.ts +3 -0
- package/dist/api/oauth.js +63 -0
- package/dist/api/util.d.ts +1 -0
- package/dist/api/util.js +11 -6
- package/dist/api/well-known.js +8 -4
- package/dist/auth-verifier.d.ts +17 -4
- package/dist/auth-verifier.js +171 -165
- package/dist/client.js +15 -8
- package/dist/config.d.ts +21 -0
- package/dist/config.js +77 -7
- package/dist/context.d.ts +11 -0
- package/dist/context.js +14 -1
- package/dist/dataplane/client/hosts.js +5 -1
- package/dist/dataplane/client/index.d.ts +2 -0
- package/dist/dataplane/client/index.js +40 -17
- package/dist/dataplane/client/util.d.ts +20 -0
- package/dist/dataplane/client/util.js +92 -0
- package/dist/dataplane/index.js +18 -2
- package/dist/dataplane/server/background.d.ts +1 -1
- package/dist/dataplane/server/background.js +12 -5
- package/dist/dataplane/server/db/database-schema.d.ts +5 -1
- package/dist/dataplane/server/db/database-schema.js +2 -1
- package/dist/dataplane/server/db/db.js +60 -20
- package/dist/dataplane/server/db/index.js +17 -1
- package/dist/dataplane/server/db/migrations/20250515T045948368Z-init.d.ts +3 -0
- package/dist/dataplane/server/db/migrations/20250515T045948368Z-init.js +170 -0
- package/dist/dataplane/server/db/migrations/20260119T210000000Z-song-recommendation.d.ts +3 -0
- package/dist/dataplane/server/db/migrations/20260119T210000000Z-song-recommendation.js +36 -0
- package/dist/dataplane/server/db/migrations/20260119T220000000Z-oauth.d.ts +3 -0
- package/dist/dataplane/server/db/migrations/20260119T220000000Z-oauth.js +25 -0
- package/dist/dataplane/server/db/migrations/index.d.ts +3 -2
- package/dist/dataplane/server/db/migrations/index.js +39 -2
- package/dist/dataplane/server/db/migrations/provider.js +5 -1
- package/dist/dataplane/server/db/pagination.d.ts +1 -1
- package/dist/dataplane/server/db/pagination.js +38 -25
- package/dist/dataplane/server/db/tables/actor-sync.d.ts +2 -2
- package/dist/dataplane/server/db/tables/actor-sync.js +4 -1
- package/dist/dataplane/server/db/tables/actor.d.ts +3 -3
- package/dist/dataplane/server/db/tables/actor.js +4 -1
- package/dist/dataplane/server/db/tables/artist-list-item.d.ts +2 -2
- package/dist/dataplane/server/db/tables/artist-list-item.js +4 -1
- package/dist/dataplane/server/db/tables/artist.js +4 -1
- package/dist/dataplane/server/db/tables/oauth-session.d.ts +9 -0
- package/dist/dataplane/server/db/tables/oauth-session.js +4 -0
- package/dist/dataplane/server/db/tables/oauth-state.d.ts +10 -0
- package/dist/dataplane/server/db/tables/oauth-state.js +4 -0
- package/dist/dataplane/server/db/tables/playlist-idea.d.ts +3 -3
- package/dist/dataplane/server/db/tables/playlist-idea.js +4 -1
- package/dist/dataplane/server/db/tables/playlist-item.js +4 -1
- package/dist/dataplane/server/db/tables/playlist.d.ts +1 -0
- package/dist/dataplane/server/db/tables/playlist.js +4 -1
- package/dist/dataplane/server/db/tables/profile.d.ts +5 -5
- package/dist/dataplane/server/db/tables/profile.js +4 -1
- package/dist/dataplane/server/db/tables/record.d.ts +1 -1
- package/dist/dataplane/server/db/tables/record.js +4 -1
- package/dist/dataplane/server/db/tables/song-recommendation.d.ts +17 -0
- package/dist/dataplane/server/db/tables/song-recommendation.js +4 -0
- package/dist/dataplane/server/db/tables/song.d.ts +2 -2
- package/dist/dataplane/server/db/tables/song.js +4 -1
- package/dist/dataplane/server/db/tables/subscription-cursor.d.ts +9 -0
- package/dist/dataplane/server/db/tables/subscription-cursor.js +4 -0
- package/dist/dataplane/server/db/types.js +2 -1
- package/dist/dataplane/server/db/util.d.ts +7 -3
- package/dist/dataplane/server/db/util.js +26 -18
- package/dist/dataplane/server/index.js +21 -13
- package/dist/dataplane/server/indexing/index.d.ts +2 -0
- package/dist/dataplane/server/indexing/index.js +82 -66
- package/dist/dataplane/server/indexing/plugins/playlist-idea.d.ts +1 -2
- package/dist/dataplane/server/indexing/plugins/playlist-idea.js +50 -41
- package/dist/dataplane/server/indexing/plugins/profile.js +45 -12
- package/dist/dataplane/server/indexing/plugins/song-recommendation.d.ts +9 -0
- package/dist/dataplane/server/indexing/plugins/song-recommendation.js +101 -0
- package/dist/dataplane/server/indexing/processor.js +12 -11
- package/dist/dataplane/server/routes/identity.d.ts +19 -0
- package/dist/dataplane/server/routes/identity.js +32 -25
- package/dist/dataplane/server/routes/index.js +15 -10
- package/dist/dataplane/server/routes/profile.js +17 -25
- package/dist/dataplane/server/routes/records.d.ts +18 -0
- package/dist/dataplane/server/routes/records.js +48 -22
- package/dist/dataplane/server/routes/sync.js +5 -3
- package/dist/dataplane/server/storage/subscription-cursor.d.ts +3 -0
- package/dist/dataplane/server/storage/subscription-cursor.js +25 -0
- package/dist/dataplane/server/subscription.d.ts +6 -3
- package/dist/dataplane/server/subscription.js +73 -63
- package/dist/error.js +9 -5
- package/dist/index.d.ts +6 -3
- package/dist/index.js +91 -28
- package/dist/lexicons/index.d.ts +3 -210
- package/dist/lexicons/index.js +26 -403
- package/dist/lexicons/lexicons.d.ts +409 -8107
- package/dist/lexicons/lexicons.js +134 -4276
- package/dist/lexicons/types/com/atproto/repo/strongRef.d.ts +4 -4
- package/dist/lexicons/types/com/atproto/repo/strongRef.js +13 -9
- package/dist/lexicons/types/com/clioplaylists/alpha/actor/profile.d.ts +10 -8
- package/dist/lexicons/types/com/clioplaylists/alpha/actor/profile.js +13 -9
- package/dist/lexicons/types/com/clioplaylists/alpha/feed/defs.d.ts +20 -11
- package/dist/lexicons/types/com/clioplaylists/alpha/feed/defs.js +29 -14
- package/dist/lexicons/types/com/clioplaylists/alpha/feed/getRecommendedPlaylistsByUser.d.ts +36 -0
- package/dist/lexicons/types/com/clioplaylists/alpha/feed/getRecommendedPlaylistsByUser.js +6 -0
- package/dist/lexicons/types/com/clioplaylists/alpha/feed/playlistIdea.d.ts +11 -14
- package/dist/lexicons/types/com/clioplaylists/alpha/feed/playlistIdea.js +20 -23
- package/dist/lexicons/types/com/clioplaylists/alpha/feed/recommendedPlaylist.d.ts +18 -0
- package/dist/lexicons/types/com/clioplaylists/alpha/feed/recommendedPlaylist.js +15 -0
- package/dist/lexicons/types/com/clioplaylists/alpha/feed/songRecommendation.d.ts +18 -0
- package/dist/lexicons/types/com/clioplaylists/alpha/feed/songRecommendation.js +15 -0
- package/dist/lexicons/util.d.ts +33 -2
- package/dist/lexicons/util.js +32 -4
- package/dist/logger.js +16 -10
- package/dist/oauth/client.d.ts +14 -0
- package/dist/oauth/client.js +126 -0
- package/dist/oauth/pds-agent.d.ts +3 -0
- package/dist/oauth/pds-agent.js +15 -0
- package/dist/rpc/clio_connect.d.ts +101 -11
- package/dist/rpc/clio_connect.js +138 -45
- package/dist/rpc/clio_pb.d.ts +448 -30
- package/dist/rpc/clio_pb.js +967 -272
- package/dist/util/retry.js +10 -9
- package/dist/util/uris.js +6 -3
- package/dist/util.d.ts +0 -1
- package/dist/util.js +61 -20
- package/package.json +26 -5
package/dist/auth-verifier.js
CHANGED
|
@@ -1,22 +1,58 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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
|
-
|
|
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
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
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
|
|
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
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
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
|
-
|
|
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
|
-
|
|
306
|
+
const authHeader = header(req, 'authorization');
|
|
307
|
+
return authHeader?.startsWith(BEARER) ?? false;
|
|
312
308
|
};
|
|
313
309
|
const isBasicToken = (req) => {
|
|
314
|
-
|
|
310
|
+
const authHeader = header(req, 'authorization');
|
|
311
|
+
return authHeader?.startsWith(BASIC) ?? false;
|
|
315
312
|
};
|
|
316
313
|
const bearerTokenFromReq = (req) => {
|
|
317
|
-
const
|
|
318
|
-
if (!
|
|
314
|
+
const authHeader = header(req, 'authorization') || '';
|
|
315
|
+
if (!authHeader.startsWith(BEARER))
|
|
319
316
|
return null;
|
|
320
|
-
return
|
|
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
|
-
|
|
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
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
+
exports.parseBasicAuth = parseBasicAuth;
|
|
342
|
+
const buildBasicAuth = (username, password) => {
|
|
343
|
+
return BASIC + ui8.toString(ui8.fromString(`${username}:${password}`, 'utf8'), 'base64pad');
|
|
341
344
|
};
|
|
342
|
-
|
|
343
|
-
|
|
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
|
|
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
|
|
353
|
+
const keyEncoder = new key_encoder_1.default('secp256k1');
|
|
349
354
|
const pemKey = keyEncoder.encodePublic(ui8.toString(publicKey, 'hex'), 'raw', 'pem');
|
|
350
|
-
const key =
|
|
351
|
-
return
|
|
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
|
-
|
|
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
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
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
|
-
|
|
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,10 +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[];
|
|
8
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;
|
|
9
19
|
}
|
|
10
20
|
export declare class ServerConfig {
|
|
11
21
|
private cfg;
|
|
@@ -17,7 +27,18 @@ export declare class ServerConfig {
|
|
|
17
27
|
get debugMode(): boolean;
|
|
18
28
|
get port(): number | undefined;
|
|
19
29
|
get localUrl(): string;
|
|
30
|
+
get publicUrl(): string;
|
|
20
31
|
get serverDid(): string;
|
|
21
32
|
get didPlcUrl(): string;
|
|
22
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;
|
|
23
44
|
}
|