@atproto/xrpc-server 0.3.2 → 0.4.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.
- package/CHANGELOG.md +23 -0
- package/LICENSE.txt +7 -0
- package/README.md +12 -4
- package/dist/auth.d.ts +4 -2
- package/dist/index.js +54 -37
- package/dist/index.js.map +3 -3
- package/dist/server.d.ts +5 -5
- package/package.json +8 -6
- package/src/auth.ts +31 -6
- package/src/server.ts +5 -4
- package/tests/auth.test.ts +109 -2
- package/tests/bodies.test.ts +2 -1
- package/tests/errors.test.ts +2 -1
- package/tests/ipld.test.ts +2 -1
- package/tests/parameters.test.ts +2 -1
- package/tests/procedures.test.ts +2 -1
- package/tests/queries.test.ts +2 -1
- package/tests/rate-limiter.test.ts +2 -1
- package/tests/responses.test.ts +2 -1
- package/tests/subscriptions.test.ts +2 -1
- package/LICENSE +0 -21
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
# @atproto/xrpc-server
|
|
2
2
|
|
|
3
|
+
## 0.4.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#1801](https://github.com/bluesky-social/atproto/pull/1801) [`ce49743d`](https://github.com/bluesky-social/atproto/commit/ce49743d7f8800d33116b88001d7b512553c2c89) Thanks [@gaearon](https://github.com/gaearon)! - Methods that accepts lexicons now take `LexiconDoc` type instead of `unknown`
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- [#1788](https://github.com/bluesky-social/atproto/pull/1788) [`84e2d4d2`](https://github.com/bluesky-social/atproto/commit/84e2d4d2b6694f344d80c18672c78b650189d423) Thanks [@bnewbold](https://github.com/bnewbold)! - update license to "MIT or Apache2"
|
|
12
|
+
|
|
13
|
+
- Updated dependencies [[`ce49743d`](https://github.com/bluesky-social/atproto/commit/ce49743d7f8800d33116b88001d7b512553c2c89), [`84e2d4d2`](https://github.com/bluesky-social/atproto/commit/84e2d4d2b6694f344d80c18672c78b650189d423)]:
|
|
14
|
+
- @atproto/lexicon@0.3.0
|
|
15
|
+
- @atproto/common@0.3.3
|
|
16
|
+
- @atproto/crypto@0.2.3
|
|
17
|
+
|
|
18
|
+
## 0.3.3
|
|
19
|
+
|
|
20
|
+
### Patch Changes
|
|
21
|
+
|
|
22
|
+
- Updated dependencies []:
|
|
23
|
+
- @atproto/common@0.3.2
|
|
24
|
+
- @atproto/lexicon@0.2.3
|
|
25
|
+
|
|
3
26
|
## 0.3.2
|
|
4
27
|
|
|
5
28
|
### Patch Changes
|
package/LICENSE.txt
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
Dual MIT/Apache-2.0 License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2022-2023 Bluesky PBC, and Contributors
|
|
4
|
+
|
|
5
|
+
Except as otherwise noted in individual files, this software is licensed under the MIT license (<http://opensource.org/licenses/MIT>), or the Apache License, Version 2.0 (<http://www.apache.org/licenses/LICENSE-2.0>).
|
|
6
|
+
|
|
7
|
+
Downstream projects and end users may chose either license individually, or both together, at their discretion. The motivation for this dual-licensing is the additional software patent assurance provided by Apache 2.0.
|
package/README.md
CHANGED
|
@@ -8,11 +8,11 @@ TypeScript library for implementing [atproto](https://atproto.com) HTTP API serv
|
|
|
8
8
|
## Usage
|
|
9
9
|
|
|
10
10
|
```typescript
|
|
11
|
+
import { LexiconDoc } from '@atproto/lexicon'
|
|
11
12
|
import * as xrpc from '@atproto/xrpc-server'
|
|
12
13
|
import express from 'express'
|
|
13
14
|
|
|
14
|
-
|
|
15
|
-
const server = xrpc.createServer([
|
|
15
|
+
const lexicons: LexiconDoc[] = [
|
|
16
16
|
{
|
|
17
17
|
lexicon: 1,
|
|
18
18
|
id: 'io.example.ping',
|
|
@@ -29,7 +29,10 @@ const server = xrpc.createServer([
|
|
|
29
29
|
},
|
|
30
30
|
},
|
|
31
31
|
},
|
|
32
|
-
]
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
// create xrpc server
|
|
35
|
+
const server = xrpc.createServer(lexicons)
|
|
33
36
|
|
|
34
37
|
function ping(ctx: {
|
|
35
38
|
auth: xrpc.HandlerAuth | undefined
|
|
@@ -51,4 +54,9 @@ app.listen(8080)
|
|
|
51
54
|
|
|
52
55
|
## License
|
|
53
56
|
|
|
54
|
-
MIT
|
|
57
|
+
This project is dual-licensed under MIT and Apache 2.0 terms:
|
|
58
|
+
|
|
59
|
+
- MIT license ([LICENSE-MIT.txt](https://github.com/bluesky-social/atproto/blob/main/LICENSE-MIT.txt) or http://opensource.org/licenses/MIT)
|
|
60
|
+
- Apache License, Version 2.0, ([LICENSE-APACHE.txt](https://github.com/bluesky-social/atproto/blob/main/LICENSE-APACHE.txt) or http://www.apache.org/licenses/LICENSE-2.0)
|
|
61
|
+
|
|
62
|
+
Downstream projects and end users may chose either license individually, or both together, at their discretion. The motivation for this dual-licensing is the additional software patent assurance provided by Apache 2.0.
|
package/dist/auth.d.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import * as crypto from '@atproto/crypto';
|
|
2
|
-
declare type
|
|
2
|
+
declare type ServiceJwtPayload = {
|
|
3
3
|
iss: string;
|
|
4
4
|
aud: string;
|
|
5
5
|
exp?: number;
|
|
6
|
+
};
|
|
7
|
+
declare type ServiceJwtParams = ServiceJwtPayload & {
|
|
6
8
|
keypair: crypto.Keypair;
|
|
7
9
|
};
|
|
8
10
|
export declare const createServiceJwt: (params: ServiceJwtParams) => Promise<string>;
|
|
@@ -11,5 +13,5 @@ export declare const createServiceAuthHeaders: (params: ServiceJwtParams) => Pro
|
|
|
11
13
|
authorization: string;
|
|
12
14
|
};
|
|
13
15
|
}>;
|
|
14
|
-
export declare const verifyJwt: (jwtStr: string, ownDid: string | null, getSigningKey: (did: string) => Promise<string>) => Promise<
|
|
16
|
+
export declare const verifyJwt: (jwtStr: string, ownDid: string | null, getSigningKey: (did: string, forceRefresh: boolean) => Promise<string>) => Promise<ServiceJwtPayload>;
|
|
15
17
|
export {};
|
package/dist/index.js
CHANGED
|
@@ -44909,6 +44909,25 @@ var validateLanguage = (langTag) => {
|
|
|
44909
44909
|
};
|
|
44910
44910
|
var bcp47Regexp = /^((?<grandfathered>(en-GB-oed|i-ami|i-bnn|i-default|i-enochian|i-hak|i-klingon|i-lux|i-mingo|i-navajo|i-pwn|i-tao|i-tay|i-tsu|sgn-BE-FR|sgn-BE-NL|sgn-CH-DE)|(art-lojban|cel-gaulish|no-bok|no-nyn|zh-guoyu|zh-hakka|zh-min|zh-min-nan|zh-xiang))|((?<language>([A-Za-z]{2,3}(-(?<extlang>[A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-(?<script>[A-Za-z]{4}))?(-(?<region>[A-Za-z]{2}|[0-9]{3}))?(-(?<variant>[A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-(?<extension>[0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(?<privateUseA>x(-[A-Za-z0-9]{1,8})+))?)|(?<privateUseB>x(-[A-Za-z0-9]{1,8})+))$/;
|
|
44911
44911
|
|
|
44912
|
+
// ../common-web/src/did-doc.ts
|
|
44913
|
+
var verificationMethod = z.object({
|
|
44914
|
+
id: z.string(),
|
|
44915
|
+
type: z.string(),
|
|
44916
|
+
controller: z.string(),
|
|
44917
|
+
publicKeyMultibase: z.string().optional()
|
|
44918
|
+
});
|
|
44919
|
+
var service = z.object({
|
|
44920
|
+
id: z.string(),
|
|
44921
|
+
type: z.string(),
|
|
44922
|
+
serviceEndpoint: z.union([z.string(), z.record(z.unknown())])
|
|
44923
|
+
});
|
|
44924
|
+
var didDocument = z.object({
|
|
44925
|
+
id: z.string(),
|
|
44926
|
+
alsoKnownAs: z.array(z.string()).optional(),
|
|
44927
|
+
verificationMethod: z.array(verificationMethod).optional(),
|
|
44928
|
+
service: z.array(service).optional()
|
|
44929
|
+
});
|
|
44930
|
+
|
|
44912
44931
|
// ../lexicon/src/validators/formats.ts
|
|
44913
44932
|
var import_iso_datestring_validator = __toESM(require_dist());
|
|
44914
44933
|
function datetime(path, value) {
|
|
@@ -45754,15 +45773,6 @@ var discriminatedObject = z.object({ $type: z.string() });
|
|
|
45754
45773
|
function isDiscriminatedObject(value) {
|
|
45755
45774
|
return discriminatedObject.safeParse(value).success;
|
|
45756
45775
|
}
|
|
45757
|
-
var LexiconDocMalformedError = class extends Error {
|
|
45758
|
-
constructor(message, schemaDef, issues) {
|
|
45759
|
-
super(message);
|
|
45760
|
-
this.schemaDef = schemaDef;
|
|
45761
|
-
this.issues = issues;
|
|
45762
|
-
this.schemaDef = schemaDef;
|
|
45763
|
-
this.issues = issues;
|
|
45764
|
-
}
|
|
45765
|
-
};
|
|
45766
45776
|
var ValidationError = class extends Error {
|
|
45767
45777
|
};
|
|
45768
45778
|
var InvalidLexiconError = class extends Error {
|
|
@@ -45843,23 +45853,13 @@ var Lexicons = class {
|
|
|
45843
45853
|
}
|
|
45844
45854
|
}
|
|
45845
45855
|
add(doc) {
|
|
45846
|
-
|
|
45847
|
-
lexiconDoc.parse(doc);
|
|
45848
|
-
} catch (e) {
|
|
45849
|
-
if (e instanceof ZodError) {
|
|
45850
|
-
throw new LexiconDocMalformedError(`Failed to parse schema definition ${doc.id}`, doc, e.issues);
|
|
45851
|
-
} else {
|
|
45852
|
-
throw e;
|
|
45853
|
-
}
|
|
45854
|
-
}
|
|
45855
|
-
const validatedDoc = doc;
|
|
45856
|
-
const uri2 = toLexUri(validatedDoc.id);
|
|
45856
|
+
const uri2 = toLexUri(doc.id);
|
|
45857
45857
|
if (this.docs.has(uri2)) {
|
|
45858
45858
|
throw new Error(`${uri2} has already been registered`);
|
|
45859
45859
|
}
|
|
45860
|
-
resolveRefUris(
|
|
45861
|
-
this.docs.set(uri2,
|
|
45862
|
-
for (const [defUri, def2] of iterDefs(
|
|
45860
|
+
resolveRefUris(doc, uri2);
|
|
45861
|
+
this.docs.set(uri2, doc);
|
|
45862
|
+
for (const [defUri, def2] of iterDefs(doc)) {
|
|
45863
45863
|
this.defs.set(defUri, def2);
|
|
45864
45864
|
}
|
|
45865
45865
|
}
|
|
@@ -51917,16 +51917,18 @@ var decompressPubkey2 = (compressed) => {
|
|
|
51917
51917
|
};
|
|
51918
51918
|
|
|
51919
51919
|
// ../crypto/src/p256/operations.ts
|
|
51920
|
-
var verifyDidSig = async (did2, data, sig) => {
|
|
51920
|
+
var verifyDidSig = async (did2, data, sig, opts) => {
|
|
51921
51921
|
const { jwtAlg, keyBytes } = parseDidKey(did2);
|
|
51922
51922
|
if (jwtAlg !== P256_JWT_ALG) {
|
|
51923
51923
|
throw new Error(`Not a P-256 did:key: ${did2}`);
|
|
51924
51924
|
}
|
|
51925
|
-
return verifySig(keyBytes, data, sig);
|
|
51925
|
+
return verifySig(keyBytes, data, sig, opts);
|
|
51926
51926
|
};
|
|
51927
|
-
var verifySig = async (publicKey, data, sig) => {
|
|
51927
|
+
var verifySig = async (publicKey, data, sig, opts) => {
|
|
51928
51928
|
const msgHash = await sha2562(data);
|
|
51929
|
-
return p256.verify(sig, msgHash, publicKey, {
|
|
51929
|
+
return p256.verify(sig, msgHash, publicKey, {
|
|
51930
|
+
lowS: opts?.lowS ?? true
|
|
51931
|
+
});
|
|
51930
51932
|
};
|
|
51931
51933
|
|
|
51932
51934
|
// ../crypto/src/p256/plugin.ts
|
|
@@ -51938,16 +51940,18 @@ var p256Plugin = {
|
|
|
51938
51940
|
var plugin_default = p256Plugin;
|
|
51939
51941
|
|
|
51940
51942
|
// ../crypto/src/secp256k1/operations.ts
|
|
51941
|
-
var verifyDidSig2 = async (did2, data, sig) => {
|
|
51943
|
+
var verifyDidSig2 = async (did2, data, sig, opts) => {
|
|
51942
51944
|
const { jwtAlg, keyBytes } = parseDidKey(did2);
|
|
51943
51945
|
if (jwtAlg !== SECP256K1_JWT_ALG) {
|
|
51944
51946
|
throw new Error(`Not a secp256k1 did:key: ${did2}`);
|
|
51945
51947
|
}
|
|
51946
|
-
return verifySig2(keyBytes, data, sig);
|
|
51948
|
+
return verifySig2(keyBytes, data, sig, opts);
|
|
51947
51949
|
};
|
|
51948
|
-
var verifySig2 = async (publicKey, data, sig) => {
|
|
51950
|
+
var verifySig2 = async (publicKey, data, sig, opts) => {
|
|
51949
51951
|
const msgHash = await sha2562(data);
|
|
51950
|
-
return secp256k1.verify(sig, msgHash, publicKey, {
|
|
51952
|
+
return secp256k1.verify(sig, msgHash, publicKey, {
|
|
51953
|
+
lowS: opts?.lowS ?? true
|
|
51954
|
+
});
|
|
51951
51955
|
};
|
|
51952
51956
|
|
|
51953
51957
|
// ../crypto/src/secp256k1/plugin.ts
|
|
@@ -51994,13 +51998,13 @@ var hasPrefix = (bytes3, prefix) => {
|
|
|
51994
51998
|
};
|
|
51995
51999
|
|
|
51996
52000
|
// ../crypto/src/verify.ts
|
|
51997
|
-
var verifySignature = (didKey, data, sig) => {
|
|
52001
|
+
var verifySignature = (didKey, data, sig, opts) => {
|
|
51998
52002
|
const parsed = parseDidKey(didKey);
|
|
51999
52003
|
const plugin = plugins_default.find((p) => p.jwtAlg === parsed.jwtAlg);
|
|
52000
52004
|
if (!plugin) {
|
|
52001
|
-
throw new Error(`Unsupported signature alg:
|
|
52005
|
+
throw new Error(`Unsupported signature alg: ${parsed.jwtAlg}`);
|
|
52002
52006
|
}
|
|
52003
|
-
return plugin.verifySignature(didKey, data, sig);
|
|
52007
|
+
return plugin.verifySignature(didKey, data, sig, opts);
|
|
52004
52008
|
};
|
|
52005
52009
|
|
|
52006
52010
|
// src/auth.ts
|
|
@@ -52045,17 +52049,30 @@ var verifyJwt = async (jwtStr, ownDid, getSigningKey) => {
|
|
|
52045
52049
|
}
|
|
52046
52050
|
const msgBytes = fromString2(parts.slice(0, 2).join("."), "utf8");
|
|
52047
52051
|
const sigBytes = fromString2(sig, "base64url");
|
|
52048
|
-
const
|
|
52052
|
+
const verifySignatureWithKey = (key) => {
|
|
52053
|
+
return verifySignature(key, msgBytes, sigBytes, {
|
|
52054
|
+
lowS: false
|
|
52055
|
+
});
|
|
52056
|
+
};
|
|
52057
|
+
const signingKey = await getSigningKey(payload.iss, false);
|
|
52049
52058
|
let validSig;
|
|
52050
52059
|
try {
|
|
52051
|
-
validSig = await
|
|
52060
|
+
validSig = await verifySignatureWithKey(signingKey);
|
|
52052
52061
|
} catch (err) {
|
|
52053
52062
|
throw new AuthRequiredError("could not verify jwt signature", "BadJwtSignature");
|
|
52054
52063
|
}
|
|
52064
|
+
if (!validSig) {
|
|
52065
|
+
const freshSigningKey = await getSigningKey(payload.iss, true);
|
|
52066
|
+
try {
|
|
52067
|
+
validSig = freshSigningKey !== signingKey ? await verifySignatureWithKey(freshSigningKey) : false;
|
|
52068
|
+
} catch (err) {
|
|
52069
|
+
throw new AuthRequiredError("could not verify jwt signature", "BadJwtSignature");
|
|
52070
|
+
}
|
|
52071
|
+
}
|
|
52055
52072
|
if (!validSig) {
|
|
52056
52073
|
throw new AuthRequiredError("jwt signature does not match jwt issuer", "BadJwtSignature");
|
|
52057
52074
|
}
|
|
52058
|
-
return payload
|
|
52075
|
+
return payload;
|
|
52059
52076
|
};
|
|
52060
52077
|
var parseB64UrlToJson = (b64) => {
|
|
52061
52078
|
return JSON.parse(b64UrlToUtf8(b64));
|