@atcute/xrpc-server 0.1.0

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 (67) hide show
  1. package/LICENSE +17 -0
  2. package/README.md +177 -0
  3. package/dist/auth/index.d.ts +2 -0
  4. package/dist/auth/index.js +3 -0
  5. package/dist/auth/index.js.map +1 -0
  6. package/dist/auth/jwt-creator.d.ts +11 -0
  7. package/dist/auth/jwt-creator.js +30 -0
  8. package/dist/auth/jwt-creator.js.map +1 -0
  9. package/dist/auth/jwt-verifier.d.ts +23 -0
  10. package/dist/auth/jwt-verifier.js +173 -0
  11. package/dist/auth/jwt-verifier.js.map +1 -0
  12. package/dist/auth/jwt.d.ts +27 -0
  13. package/dist/auth/jwt.js +96 -0
  14. package/dist/auth/jwt.js.map +1 -0
  15. package/dist/auth/types.d.ts +4 -0
  16. package/dist/auth/types.js +2 -0
  17. package/dist/auth/types.js.map +1 -0
  18. package/dist/main/index.d.ts +3 -0
  19. package/dist/main/index.js +4 -0
  20. package/dist/main/index.js.map +1 -0
  21. package/dist/main/response.d.ts +8 -0
  22. package/dist/main/response.js +4 -0
  23. package/dist/main/response.js.map +1 -0
  24. package/dist/main/router.d.ts +21 -0
  25. package/dist/main/router.js +158 -0
  26. package/dist/main/router.js.map +1 -0
  27. package/dist/main/types/operation.d.ts +36 -0
  28. package/dist/main/types/operation.js +2 -0
  29. package/dist/main/types/operation.js.map +1 -0
  30. package/dist/main/utils/middlewares.d.ts +2 -0
  31. package/dist/main/utils/middlewares.js +5 -0
  32. package/dist/main/utils/middlewares.js.map +1 -0
  33. package/dist/main/utils/request-input.d.ts +3 -0
  34. package/dist/main/utils/request-input.js +50 -0
  35. package/dist/main/utils/request-input.js.map +1 -0
  36. package/dist/main/utils/request-params.d.ts +5 -0
  37. package/dist/main/utils/request-params.js +80 -0
  38. package/dist/main/utils/request-params.js.map +1 -0
  39. package/dist/main/utils/response.d.ts +3 -0
  40. package/dist/main/utils/response.js +8 -0
  41. package/dist/main/utils/response.js.map +1 -0
  42. package/dist/main/xrpc-error.d.ts +39 -0
  43. package/dist/main/xrpc-error.js +58 -0
  44. package/dist/main/xrpc-error.js.map +1 -0
  45. package/dist/middlewares/cors.d.ts +8 -0
  46. package/dist/middlewares/cors.js +42 -0
  47. package/dist/middlewares/cors.js.map +1 -0
  48. package/dist/types/misc.d.ts +9 -0
  49. package/dist/types/misc.js +2 -0
  50. package/dist/types/misc.js.map +1 -0
  51. package/lib/auth/index.ts +2 -0
  52. package/lib/auth/jwt-creator.ts +51 -0
  53. package/lib/auth/jwt-verifier.ts +215 -0
  54. package/lib/auth/jwt.ts +124 -0
  55. package/lib/auth/types.ts +4 -0
  56. package/lib/main/index.ts +3 -0
  57. package/lib/main/response.ts +9 -0
  58. package/lib/main/router.ts +237 -0
  59. package/lib/main/types/operation.ts +87 -0
  60. package/lib/main/utils/middlewares.ts +13 -0
  61. package/lib/main/utils/request-input.ts +64 -0
  62. package/lib/main/utils/request-params.ts +108 -0
  63. package/lib/main/utils/response.ts +14 -0
  64. package/lib/main/xrpc-error.ts +80 -0
  65. package/lib/middlewares/cors.ts +68 -0
  66. package/lib/types/misc.ts +4 -0
  67. package/package.json +44 -0
package/LICENSE ADDED
@@ -0,0 +1,17 @@
1
+ Permission is hereby granted, free of charge, to any person obtaining a copy
2
+ of this software and associated documentation files (the "Software"), to deal
3
+ in the Software without restriction, including without limitation the rights
4
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
5
+ copies of the Software, and to permit persons to whom the Software is
6
+ furnished to do so, subject to the following conditions:
7
+
8
+ The above copyright notice and this permission notice shall be included in all
9
+ copies or substantial portions of the Software.
10
+
11
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
12
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
13
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
14
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
15
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
16
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
17
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,177 @@
1
+ # @atcute/xrpc-server
2
+
3
+ a small web framework for handling XRPC operations.
4
+
5
+ ## quick start
6
+
7
+ this framework relies on schemas generated by `@atcute/lex-cli`, you'd need to follow its
8
+ [quick start guide](../../lexicons/lex-cli/README.md#quick-start) on how to set it up.
9
+
10
+ for this example, we'll define a very simple query operation, one that returns a message greeting
11
+ the name that's provided to it:
12
+
13
+ ```json
14
+ // file: lexicons/com/example/greet.json
15
+ {
16
+ "lexicon": 1,
17
+ "id": "com.example.greet",
18
+ "defs": {
19
+ "main": {
20
+ "type": "query",
21
+ "parameters": {
22
+ "type": "params",
23
+ "required": ["name"],
24
+ "properties": {
25
+ "name": {
26
+ "type": "string"
27
+ }
28
+ }
29
+ },
30
+ "output": {
31
+ "encoding": "application/json",
32
+ "schema": {
33
+ "type": "object",
34
+ "required": ["message"],
35
+ "properties": {
36
+ "message": {
37
+ "type": "string"
38
+ }
39
+ }
40
+ }
41
+ }
42
+ }
43
+ }
44
+ }
45
+ ```
46
+
47
+ now we can build a server using the TypeScript schemas:
48
+
49
+ ```ts
50
+ // file: src/index.js
51
+ import { XRPCRouter, json } from '@atcute/xrpc-server';
52
+ import { cors } from '@atucte/xrpc-server/middlewares/cors';
53
+
54
+ import { ComExampleGreet } from './lexicons/index.js';
55
+
56
+ const router = new XRPCRouter({ middlewares: [cors()] });
57
+
58
+ router.add(ComExampleGreet.mainSchema, {
59
+ async handler({ params: { name } }) {
60
+ return json({ message: `hello ${name}!` });
61
+ },
62
+ });
63
+
64
+ export default router;
65
+ ```
66
+
67
+ on Deno, Bun or Cloudflare Workers, you can export the router directly and expect it to work out of
68
+ the box.
69
+
70
+ but for Node.js, you'll need the [`@hono/node-server`][hono-node-server] adapter as the router works
71
+ with standard Web Request/Response:
72
+
73
+ [hono-node-server]: https://github.com/honojs/node-server
74
+
75
+ ```ts
76
+ // file: src/index.js
77
+ import { XRPCRouter } from '@atcute/xrpc-server';
78
+ import { serve } from '@hono/node-server';
79
+
80
+ const router = new XRPCRouter();
81
+
82
+ // ... handler code ...
83
+
84
+ serve(
85
+ {
86
+ fetch: router.fetch,
87
+ port: 3000,
88
+ },
89
+ (info) => {
90
+ console.log(`listening on port ${info.port}`);
91
+ },
92
+ );
93
+ ```
94
+
95
+ ## kitchen sink
96
+
97
+ ### Bluesky feed generator example
98
+
99
+ ```ts
100
+ import { parseCanonicalResourceUri, type Nsid } from '@atcute/lexicons';
101
+
102
+ import { AuthRequiredError, InvalidRequestError, XRPCRouter, json } from '@atcute/xrpc-server';
103
+ import { ServiceJwtVerifier, type VerifiedJwt } from '@atcute/xrpc-server/auth';
104
+ import { cors } from '@atucte/xrpc-server/middlewares/cors';
105
+
106
+ import {
107
+ CompositeDidDocumentResolver,
108
+ PlcDidDocumentResolver,
109
+ WebDidDocumentResolver,
110
+ } from '@atcute/identity-resolver';
111
+
112
+ import { AppBskyFeedGetFeedSkeleton } from '@atcute/bluesky';
113
+
114
+ const SERVICE_DID = 'did:web:feedgen.example.com';
115
+
116
+ const router = new XRPCRouter({
117
+ middlewares: [cors()],
118
+ });
119
+
120
+ const jwtVerifier = new ServiceJwtVerifier({
121
+ serviceDid: SERVICE_DID,
122
+ resolver: new CompositeDidDocumentResolver({
123
+ methods: {
124
+ plc: new PlcDidDocumentResolver(),
125
+ web: new WebDidDocumentResolver(),
126
+ },
127
+ }),
128
+ });
129
+
130
+ const requireAuth = async (request: Request, lxm: Nsid): Promise<VerifiedJwt> => {
131
+ const auth = request.headers.get('authorization');
132
+ if (auth === null) {
133
+ throw new AuthRequiredError({ description: `missing authorization header` });
134
+ }
135
+ if (!auth.startsWith('Bearer ')) {
136
+ throw new AuthRequiredError({ description: `invalid authorization scheme` });
137
+ }
138
+
139
+ const jwtString = auth.slice('Bearer '.length).trim();
140
+
141
+ const result = await jwtVerifier.verify(jwtString, { lxm });
142
+ if (!result.ok) {
143
+ throw new AuthRequiredError(result.error);
144
+ }
145
+
146
+ return result.value;
147
+ };
148
+
149
+ router.add(AppBskyFeedGetFeedSkeleton.mainSchema, {
150
+ async handler({ params: { feed }, request }) {
151
+ await requireAuth(request, 'app.bsky.feed.getFeedSkeleton');
152
+
153
+ const feedUri = parseCanonicalResourceUri(feed);
154
+
155
+ if (
156
+ !feedUri.ok ||
157
+ feedUri.value.collection !== 'app.bsky.feed.generator' ||
158
+ feedUri.value.repo !== SERVICE_DID ||
159
+ feedUri.value.rkey !== 'feed'
160
+ ) {
161
+ throw new InvalidRequestError({
162
+ error: 'InvalidFeed',
163
+ description: `invalid feed`,
164
+ });
165
+ }
166
+
167
+ return json({
168
+ feed: [
169
+ { post: 'at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.post/3l6oveex3ii2l' },
170
+ { post: 'at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.post/3lpk2lf7k6k2t' },
171
+ ],
172
+ });
173
+ },
174
+ });
175
+
176
+ export default router;
177
+ ```
@@ -0,0 +1,2 @@
1
+ export * from './jwt-creator.js';
2
+ export * from './jwt-verifier.js';
@@ -0,0 +1,3 @@
1
+ export * from './jwt-creator.js';
2
+ export * from './jwt-verifier.js';
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../lib/auth/index.ts"],"names":[],"mappings":"AAAA,cAAc,kBAAkB,CAAC;AACjC,cAAc,mBAAmB,CAAC"}
@@ -0,0 +1,11 @@
1
+ import type { PrivateKey } from '@atcute/crypto';
2
+ import type { Did, Nsid } from '@atcute/lexicons';
3
+ export interface CreateServiceJwtOptions {
4
+ keypair: PrivateKey;
5
+ issuer: Did;
6
+ audience: Did;
7
+ lxm: Nsid | null;
8
+ issuedAt?: number;
9
+ expiresIn?: number;
10
+ }
11
+ export declare const createServiceJwt: (options: CreateServiceJwtOptions) => Promise<string>;
@@ -0,0 +1,30 @@
1
+ import { toBase64Url } from '@atcute/multibase';
2
+ import { encodeUtf8 } from '@atcute/uint8array';
3
+ import { nanoid } from 'nanoid';
4
+ export const createServiceJwt = async (options) => {
5
+ const keypair = options.keypair;
6
+ const issuedAt = Math.floor(options.issuedAt ?? Date.now() / 1_000);
7
+ const expiresIn = Math.floor(options.expiresIn ?? 60);
8
+ const header = {
9
+ typ: 'JWT',
10
+ alg: keypair.jwtAlg,
11
+ };
12
+ const payload = {
13
+ aud: options.audience,
14
+ exp: issuedAt + expiresIn,
15
+ iat: issuedAt,
16
+ iss: options.issuer,
17
+ jti: nanoid(24),
18
+ lxm: options.lxm ?? undefined,
19
+ };
20
+ const headerB64 = encodeJwtPortion(header);
21
+ const payloadB64 = encodeJwtPortion(payload);
22
+ const message = `${headerB64}.${payloadB64}`;
23
+ const signature = await keypair.sign(encodeUtf8(message));
24
+ const signatureB64 = toBase64Url(signature);
25
+ return `${message}.${signatureB64}`;
26
+ };
27
+ const encodeJwtPortion = (data) => {
28
+ return toBase64Url(encodeUtf8(JSON.stringify(data)));
29
+ };
30
+ //# sourceMappingURL=jwt-creator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jwt-creator.js","sourceRoot":"","sources":["../../lib/auth/jwt-creator.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAEhD,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAahC,MAAM,CAAC,MAAM,gBAAgB,GAAG,KAAK,EAAE,OAAgC,EAAmB,EAAE;IAC3F,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;IAEhC,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,QAAQ,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC,CAAC;IACpE,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC;IAEtD,MAAM,MAAM,GAAc;QACzB,GAAG,EAAE,KAAK;QACV,GAAG,EAAE,OAAO,CAAC,MAAM;KACnB,CAAC;IAEF,MAAM,OAAO,GAAe;QAC3B,GAAG,EAAE,OAAO,CAAC,QAAQ;QACrB,GAAG,EAAE,QAAQ,GAAG,SAAS;QACzB,GAAG,EAAE,QAAQ;QACb,GAAG,EAAE,OAAO,CAAC,MAAM;QACnB,GAAG,EAAE,MAAM,CAAC,EAAE,CAAC;QACf,GAAG,EAAE,OAAO,CAAC,GAAG,IAAI,SAAS;KAC7B,CAAC;IAEF,MAAM,SAAS,GAAG,gBAAgB,CAAC,MAAM,CAAC,CAAC;IAC3C,MAAM,UAAU,GAAG,gBAAgB,CAAC,OAAO,CAAC,CAAC;IAC7C,MAAM,OAAO,GAAG,GAAG,SAAS,IAAI,UAAU,EAAE,CAAC;IAE7C,MAAM,SAAS,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC;IAC1D,MAAM,YAAY,GAAG,WAAW,CAAC,SAAS,CAAC,CAAC;IAE5C,OAAO,GAAG,OAAO,IAAI,YAAY,EAAE,CAAC;AACrC,CAAC,CAAC;AAEF,MAAM,gBAAgB,GAAG,CAAC,IAAa,EAAU,EAAE;IAClD,OAAO,WAAW,CAAC,UAAU,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AACtD,CAAC,CAAC"}
@@ -0,0 +1,23 @@
1
+ import { type DidDocumentResolver } from '@atcute/identity-resolver';
2
+ import type { Did, Nsid } from '@atcute/lexicons';
3
+ import type { Result } from '../types/misc.js';
4
+ import type { AuthError } from './types.js';
5
+ export interface ServiceJwtVerifierOptions {
6
+ serviceDid: Did | null;
7
+ resolver: DidDocumentResolver;
8
+ }
9
+ export interface VerifyJwtOptions {
10
+ lxm: Nsid | Nsid[] | null;
11
+ }
12
+ export interface VerifiedJwt {
13
+ issuer: Did;
14
+ audience: Did;
15
+ lxm: string | undefined;
16
+ }
17
+ export declare class ServiceJwtVerifier {
18
+ #private;
19
+ didDocResolver: DidDocumentResolver;
20
+ serviceDid: Did | null;
21
+ constructor(options: ServiceJwtVerifierOptions);
22
+ verify(jwtString: string, options?: VerifyJwtOptions): Promise<Result<VerifiedJwt, AuthError>>;
23
+ }
@@ -0,0 +1,173 @@
1
+ import { getPublicKeyFromDidController, verifySig } from '@atcute/crypto';
2
+ import { getAtprotoVerificationMaterial } from '@atcute/identity';
3
+ import {} from '@atcute/identity-resolver';
4
+ import * as uint8arrays from '@atcute/uint8array';
5
+ import { parseJwt } from './jwt.js';
6
+ export class ServiceJwtVerifier {
7
+ didDocResolver;
8
+ serviceDid;
9
+ constructor(options) {
10
+ this.didDocResolver = options.resolver;
11
+ this.serviceDid = options.serviceDid;
12
+ }
13
+ async #getSigningKey(issuer, noCache) {
14
+ let didDocument;
15
+ let key;
16
+ try {
17
+ didDocument = await this.didDocResolver.resolve(issuer, { noCache });
18
+ }
19
+ catch {
20
+ return {
21
+ ok: false,
22
+ error: {
23
+ error: 'UnresolvedDidDocument',
24
+ description: `failed to retrieve did document for ${issuer}`,
25
+ },
26
+ };
27
+ }
28
+ const controller = getAtprotoVerificationMaterial(didDocument);
29
+ if (!controller) {
30
+ return {
31
+ ok: false,
32
+ error: {
33
+ error: 'BadJwtIssuer',
34
+ description: `${issuer} does not have an atproto verification material`,
35
+ },
36
+ };
37
+ }
38
+ try {
39
+ key = getPublicKeyFromDidController(controller);
40
+ }
41
+ catch {
42
+ return {
43
+ ok: false,
44
+ error: {
45
+ error: 'BadJwtIssuer',
46
+ description: `${issuer} has invalid atproto verification material`,
47
+ },
48
+ };
49
+ }
50
+ return { ok: true, value: key };
51
+ }
52
+ async #verifySignature(key, jwt) {
53
+ try {
54
+ return {
55
+ ok: true,
56
+ value: await verifySig(key, jwt.signature, jwt.message, { allowMalleableSig: true }),
57
+ };
58
+ }
59
+ catch {
60
+ return {
61
+ ok: false,
62
+ error: {
63
+ error: 'BadJwtSignature',
64
+ description: `could not verify jwt signature`,
65
+ },
66
+ };
67
+ }
68
+ }
69
+ async verify(jwtString, options) {
70
+ const parsed = parseJwt(jwtString);
71
+ if (!parsed.ok) {
72
+ return parsed;
73
+ }
74
+ const { header, payload } = parsed.value;
75
+ switch (header.typ) {
76
+ case 'at+jwt':
77
+ case 'refresh+jwt':
78
+ case 'dpop+jwt': {
79
+ return {
80
+ ok: false,
81
+ error: {
82
+ error: 'BadJwtType',
83
+ description: `invalid jwt type`,
84
+ },
85
+ };
86
+ }
87
+ }
88
+ if (Date.now() / 1_000 > payload.exp) {
89
+ return {
90
+ ok: false,
91
+ error: {
92
+ error: 'JwtExpired',
93
+ description: `jwt is expired`,
94
+ },
95
+ };
96
+ }
97
+ if (this.serviceDid !== undefined && this.serviceDid !== payload.aud) {
98
+ return {
99
+ ok: false,
100
+ error: {
101
+ error: 'BadJwtAudience',
102
+ description: `jwt audience does not match (expected ${this.serviceDid})`,
103
+ },
104
+ };
105
+ }
106
+ if (options?.lxm != null &&
107
+ (typeof options.lxm === 'string' ? options.lxm !== payload.lxm : !options.lxm.includes(payload.lxm))) {
108
+ return {
109
+ ok: false,
110
+ error: {
111
+ error: `BadJwtLexiconMethod`,
112
+ description: `jwt lexicon method does not match (expected ${options.lxm})`,
113
+ },
114
+ };
115
+ }
116
+ const key = await this.#getSigningKey(payload.iss, false);
117
+ if (!key.ok) {
118
+ return key;
119
+ }
120
+ let isValid = false;
121
+ if (key.value.jwtAlg === header.alg) {
122
+ const result = await this.#verifySignature(key.value, parsed.value);
123
+ if (!result.ok) {
124
+ return result;
125
+ }
126
+ isValid = result.value;
127
+ }
128
+ if (!isValid) {
129
+ // try again, uncached
130
+ const freshKey = await this.#getSigningKey(payload.iss, true);
131
+ if (!freshKey.ok) {
132
+ return freshKey;
133
+ }
134
+ // at this point we can't ignore the jwt alg difference
135
+ if (freshKey.value.jwtAlg !== header.alg) {
136
+ return {
137
+ ok: false,
138
+ error: {
139
+ error: 'BadJwtIssuer',
140
+ description: `mismatching cryptographic key format (jwt is ${header.alg})`,
141
+ },
142
+ };
143
+ }
144
+ // only revalidate if it's a different key
145
+ if (!uint8arrays.equals(freshKey.value.publicKeyBytes, key.value.publicKeyBytes)) {
146
+ const result = await this.#verifySignature(key.value, parsed.value);
147
+ if (!result.ok) {
148
+ return result;
149
+ }
150
+ isValid = result.value;
151
+ }
152
+ }
153
+ if (!isValid) {
154
+ // too bad
155
+ return {
156
+ ok: false,
157
+ error: {
158
+ error: 'BadJwtSignature',
159
+ description: `invalid jwt signature`,
160
+ },
161
+ };
162
+ }
163
+ return {
164
+ ok: true,
165
+ value: {
166
+ issuer: payload.iss,
167
+ audience: payload.aud,
168
+ lxm: payload.lxm,
169
+ },
170
+ };
171
+ }
172
+ }
173
+ //# sourceMappingURL=jwt-verifier.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jwt-verifier.js","sourceRoot":"","sources":["../../lib/auth/jwt-verifier.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,27 @@
1
+ import * as v from '@badrap/valita';
2
+ import type { Result } from '../types/misc.js';
3
+ import type { AuthError } from './types.js';
4
+ declare const jwtHeader: v.ObjectType<{
5
+ typ: v.Optional<string>;
6
+ alg: v.Type<string>;
7
+ }, undefined>;
8
+ export interface JwtHeader extends v.Infer<typeof jwtHeader> {
9
+ }
10
+ declare const jwtPayload: v.Type<{
11
+ iat?: number | undefined;
12
+ lxm?: `${string}.${string}.${string}` | undefined;
13
+ jti?: string | undefined;
14
+ iss: `did:${string}:${string}`;
15
+ aud: `did:${string}:${string}`;
16
+ exp: number;
17
+ }>;
18
+ export interface JwtPayload extends v.Infer<typeof jwtPayload> {
19
+ }
20
+ export interface ParsedJwt {
21
+ header: JwtHeader;
22
+ payload: JwtPayload;
23
+ message: Uint8Array;
24
+ signature: Uint8Array;
25
+ }
26
+ export declare const parseJwt: (jwtString: string) => Result<ParsedJwt, AuthError>;
27
+ export {};
@@ -0,0 +1,96 @@
1
+ import * as v from '@badrap/valita';
2
+ import { isDid, isNsid } from '@atcute/lexicons/syntax';
3
+ import { fromBase64Url } from '@atcute/multibase';
4
+ import { decodeUtf8From, encodeUtf8 } from '@atcute/uint8array';
5
+ const didString = v.string().assert(isDid, `must be a did`);
6
+ const nsidString = v.string().assert(isNsid, `must be an nsid`);
7
+ const integer = v.number().assert((input) => input >= 0 && Number.isSafeInteger(input), `must be an integer`);
8
+ const jwtHeader = v.object({
9
+ typ: v.string().optional(),
10
+ alg: v.string(),
11
+ });
12
+ const jwtPayload = v
13
+ .object({
14
+ /** issuer */
15
+ iss: didString,
16
+ /** target audience */
17
+ aud: didString,
18
+ /** expiration time */
19
+ exp: integer,
20
+ /** creation time */
21
+ iat: integer.optional(),
22
+ /** xrpc operation being invoked */
23
+ lxm: nsidString.optional(),
24
+ /** unique identifier */
25
+ jti: v.string().optional(),
26
+ })
27
+ .assert(({ iat, exp }) => iat === undefined || exp > iat, {
28
+ message: `expiry time must be greater than issued time`,
29
+ path: ['exp'],
30
+ });
31
+ const readJwtPortion = (schema, input) => {
32
+ try {
33
+ const raw = decodeUtf8From(fromBase64Url(input));
34
+ const json = JSON.parse(raw);
35
+ const result = schema.try(json);
36
+ if (result.ok) {
37
+ return result;
38
+ }
39
+ }
40
+ catch { }
41
+ return {
42
+ ok: false,
43
+ error: {
44
+ error: `MalformedJwt`,
45
+ description: `jwt is malformed`,
46
+ },
47
+ };
48
+ };
49
+ const readJwtSignature = (input) => {
50
+ try {
51
+ return { ok: true, value: fromBase64Url(input) };
52
+ }
53
+ catch { }
54
+ return {
55
+ ok: false,
56
+ error: {
57
+ error: `MalformedJwt`,
58
+ description: `jwt is malformed`,
59
+ },
60
+ };
61
+ };
62
+ export const parseJwt = (jwtString) => {
63
+ const parts = jwtString.split('.');
64
+ if (parts.length !== 3) {
65
+ return {
66
+ ok: false,
67
+ error: {
68
+ error: `MalformedJwt`,
69
+ description: `jwt is malformed`,
70
+ },
71
+ };
72
+ }
73
+ const [headerString, payloadString, signatureString] = parts;
74
+ const header = readJwtPortion(jwtHeader, headerString);
75
+ if (!header.ok) {
76
+ return header;
77
+ }
78
+ const payload = readJwtPortion(jwtPayload, payloadString);
79
+ if (!payload.ok) {
80
+ return payload;
81
+ }
82
+ const signature = readJwtSignature(signatureString);
83
+ if (!signature.ok) {
84
+ return signature;
85
+ }
86
+ return {
87
+ ok: true,
88
+ value: {
89
+ header: header.value,
90
+ payload: payload.value,
91
+ message: encodeUtf8(`${headerString}.${payloadString}`),
92
+ signature: signature.value,
93
+ },
94
+ };
95
+ };
96
+ //# sourceMappingURL=jwt.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jwt.js","sourceRoot":"","sources":["../../lib/auth/jwt.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ export type AuthError = {
2
+ error: string;
3
+ description: string;
4
+ };
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../lib/auth/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,3 @@
1
+ export * from './response.js';
2
+ export * from './router.js';
3
+ export * from './xrpc-error.js';
@@ -0,0 +1,4 @@
1
+ export * from './response.js';
2
+ export * from './router.js';
3
+ export * from './xrpc-error.js';
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../lib/main/index.ts"],"names":[],"mappings":"AAAA,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC;AAC5B,cAAc,iBAAiB,CAAC"}
@@ -0,0 +1,8 @@
1
+ declare const kJson: unique symbol;
2
+ export type JSONResponse<TData> = Response & {
3
+ [kJson]: TData;
4
+ };
5
+ export declare const json: {
6
+ <TData>(data: NoInfer<TData>, init?: ResponseInit): JSONResponse<TData>;
7
+ };
8
+ export {};
@@ -0,0 +1,4 @@
1
+ export const json = (data, init) => {
2
+ return Response.json(data, init);
3
+ };
4
+ //# sourceMappingURL=response.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"response.js","sourceRoot":"","sources":["../../lib/main/response.ts"],"names":[],"mappings":"AAIA,MAAM,CAAC,MAAM,IAAI,GAEb,CAAC,IAAS,EAAE,IAAmB,EAAO,EAAE;IAC3C,OAAO,QAAQ,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;AAClC,CAAC,CAAC"}
@@ -0,0 +1,21 @@
1
+ import { type XRPCProcedureMetadata, type XRPCQueryMetadata } from '@atcute/lexicons/validations';
2
+ import type { Promisable } from '../types/misc.js';
3
+ import type { ProcedureConfig, QueryConfig } from './types/operation.js';
4
+ import { type Middleware } from './utils/middlewares.js';
5
+ export type FetchMiddleware = Middleware<[request: Request], Promise<Response>>;
6
+ export type NotFoundHandler = (request: Request) => Promisable<Response>;
7
+ export type ExceptionHandler = (error: unknown, request: Request) => Promisable<Response>;
8
+ export declare const defaultExceptionHandler: ExceptionHandler;
9
+ export declare const defaultNotFoundHandler: NotFoundHandler;
10
+ export interface XRPCRouterOptions {
11
+ middlewares?: FetchMiddleware[];
12
+ handleNotFound?: NotFoundHandler;
13
+ handleException?: ExceptionHandler;
14
+ }
15
+ export declare class XRPCRouter {
16
+ #private;
17
+ fetch: (request: Request) => Promise<Response>;
18
+ constructor({ middlewares, handleException, handleNotFound, }?: XRPCRouterOptions);
19
+ add<TQuery extends XRPCQueryMetadata>(query: TQuery, config: QueryConfig<TQuery>): void;
20
+ add<TProcedure extends XRPCProcedureMetadata>(procedure: TProcedure, config: ProcedureConfig<TProcedure>): void;
21
+ }