@atproto/xrpc-server 0.3.3 → 0.4.1
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 +24 -0
- package/LICENSE.txt +7 -0
- package/README.md +12 -4
- package/dist/auth.d.ts +4 -2
- package/dist/index.js +60 -38
- package/dist/index.js.map +2 -2
- 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/src/util.ts +1 -1
- 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,29 @@
|
|
|
1
1
|
# @atproto/xrpc-server
|
|
2
2
|
|
|
3
|
+
## 0.4.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#1839](https://github.com/bluesky-social/atproto/pull/1839) [`e1b5f253`](https://github.com/bluesky-social/atproto/commit/e1b5f2537a5ba4d8b951a741269b604856028ae5) Thanks [@dholms](https://github.com/dholms)! - Prevent signature malleability through DER-encoded signatures
|
|
8
|
+
|
|
9
|
+
- Updated dependencies [[`e1b5f253`](https://github.com/bluesky-social/atproto/commit/e1b5f2537a5ba4d8b951a741269b604856028ae5)]:
|
|
10
|
+
- @atproto/crypto@0.3.0
|
|
11
|
+
|
|
12
|
+
## 0.4.0
|
|
13
|
+
|
|
14
|
+
### Minor Changes
|
|
15
|
+
|
|
16
|
+
- [#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`
|
|
17
|
+
|
|
18
|
+
### Patch Changes
|
|
19
|
+
|
|
20
|
+
- [#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"
|
|
21
|
+
|
|
22
|
+
- Updated dependencies [[`ce49743d`](https://github.com/bluesky-social/atproto/commit/ce49743d7f8800d33116b88001d7b512553c2c89), [`84e2d4d2`](https://github.com/bluesky-social/atproto/commit/84e2d4d2b6694f344d80c18672c78b650189d423)]:
|
|
23
|
+
- @atproto/lexicon@0.3.0
|
|
24
|
+
- @atproto/common@0.3.3
|
|
25
|
+
- @atproto/crypto@0.2.3
|
|
26
|
+
|
|
3
27
|
## 0.3.3
|
|
4
28
|
|
|
5
29
|
### 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
|
@@ -45773,15 +45773,6 @@ var discriminatedObject = z.object({ $type: z.string() });
|
|
|
45773
45773
|
function isDiscriminatedObject(value) {
|
|
45774
45774
|
return discriminatedObject.safeParse(value).success;
|
|
45775
45775
|
}
|
|
45776
|
-
var LexiconDocMalformedError = class extends Error {
|
|
45777
|
-
constructor(message, schemaDef, issues) {
|
|
45778
|
-
super(message);
|
|
45779
|
-
this.schemaDef = schemaDef;
|
|
45780
|
-
this.issues = issues;
|
|
45781
|
-
this.schemaDef = schemaDef;
|
|
45782
|
-
this.issues = issues;
|
|
45783
|
-
}
|
|
45784
|
-
};
|
|
45785
45776
|
var ValidationError = class extends Error {
|
|
45786
45777
|
};
|
|
45787
45778
|
var InvalidLexiconError = class extends Error {
|
|
@@ -45862,23 +45853,13 @@ var Lexicons = class {
|
|
|
45862
45853
|
}
|
|
45863
45854
|
}
|
|
45864
45855
|
add(doc) {
|
|
45865
|
-
|
|
45866
|
-
lexiconDoc.parse(doc);
|
|
45867
|
-
} catch (e) {
|
|
45868
|
-
if (e instanceof ZodError) {
|
|
45869
|
-
throw new LexiconDocMalformedError(`Failed to parse schema definition ${doc.id}`, doc, e.issues);
|
|
45870
|
-
} else {
|
|
45871
|
-
throw e;
|
|
45872
|
-
}
|
|
45873
|
-
}
|
|
45874
|
-
const validatedDoc = doc;
|
|
45875
|
-
const uri2 = toLexUri(validatedDoc.id);
|
|
45856
|
+
const uri2 = toLexUri(doc.id);
|
|
45876
45857
|
if (this.docs.has(uri2)) {
|
|
45877
45858
|
throw new Error(`${uri2} has already been registered`);
|
|
45878
45859
|
}
|
|
45879
|
-
resolveRefUris(
|
|
45880
|
-
this.docs.set(uri2,
|
|
45881
|
-
for (const [defUri, def2] of iterDefs(
|
|
45860
|
+
resolveRefUris(doc, uri2);
|
|
45861
|
+
this.docs.set(uri2, doc);
|
|
45862
|
+
for (const [defUri, def2] of iterDefs(doc)) {
|
|
45882
45863
|
this.defs.set(defUri, def2);
|
|
45883
45864
|
}
|
|
45884
45865
|
}
|
|
@@ -51936,16 +51917,30 @@ var decompressPubkey2 = (compressed) => {
|
|
|
51936
51917
|
};
|
|
51937
51918
|
|
|
51938
51919
|
// ../crypto/src/p256/operations.ts
|
|
51939
|
-
var verifyDidSig = async (did2, data, sig) => {
|
|
51920
|
+
var verifyDidSig = async (did2, data, sig, opts) => {
|
|
51940
51921
|
const { jwtAlg, keyBytes } = parseDidKey(did2);
|
|
51941
51922
|
if (jwtAlg !== P256_JWT_ALG) {
|
|
51942
51923
|
throw new Error(`Not a P-256 did:key: ${did2}`);
|
|
51943
51924
|
}
|
|
51944
|
-
return verifySig(keyBytes, data, sig);
|
|
51925
|
+
return verifySig(keyBytes, data, sig, opts);
|
|
51945
51926
|
};
|
|
51946
|
-
var verifySig = async (publicKey, data, sig) => {
|
|
51927
|
+
var verifySig = async (publicKey, data, sig, opts) => {
|
|
51928
|
+
const allowMalleable = opts?.allowMalleableSig ?? false;
|
|
51947
51929
|
const msgHash = await sha2562(data);
|
|
51948
|
-
|
|
51930
|
+
if (!allowMalleable && !isCompactFormat(sig)) {
|
|
51931
|
+
return false;
|
|
51932
|
+
}
|
|
51933
|
+
return p256.verify(sig, msgHash, publicKey, {
|
|
51934
|
+
lowS: !allowMalleable
|
|
51935
|
+
});
|
|
51936
|
+
};
|
|
51937
|
+
var isCompactFormat = (sig) => {
|
|
51938
|
+
try {
|
|
51939
|
+
const parsed = p256.Signature.fromCompact(sig);
|
|
51940
|
+
return equals3(parsed.toCompactRawBytes(), sig);
|
|
51941
|
+
} catch {
|
|
51942
|
+
return false;
|
|
51943
|
+
}
|
|
51949
51944
|
};
|
|
51950
51945
|
|
|
51951
51946
|
// ../crypto/src/p256/plugin.ts
|
|
@@ -51957,16 +51952,30 @@ var p256Plugin = {
|
|
|
51957
51952
|
var plugin_default = p256Plugin;
|
|
51958
51953
|
|
|
51959
51954
|
// ../crypto/src/secp256k1/operations.ts
|
|
51960
|
-
var verifyDidSig2 = async (did2, data, sig) => {
|
|
51955
|
+
var verifyDidSig2 = async (did2, data, sig, opts) => {
|
|
51961
51956
|
const { jwtAlg, keyBytes } = parseDidKey(did2);
|
|
51962
51957
|
if (jwtAlg !== SECP256K1_JWT_ALG) {
|
|
51963
51958
|
throw new Error(`Not a secp256k1 did:key: ${did2}`);
|
|
51964
51959
|
}
|
|
51965
|
-
return verifySig2(keyBytes, data, sig);
|
|
51960
|
+
return verifySig2(keyBytes, data, sig, opts);
|
|
51966
51961
|
};
|
|
51967
|
-
var verifySig2 = async (publicKey, data, sig) => {
|
|
51962
|
+
var verifySig2 = async (publicKey, data, sig, opts) => {
|
|
51963
|
+
const allowMalleable = opts?.allowMalleableSig ?? false;
|
|
51968
51964
|
const msgHash = await sha2562(data);
|
|
51969
|
-
|
|
51965
|
+
if (!allowMalleable && !isCompactFormat2(sig)) {
|
|
51966
|
+
return false;
|
|
51967
|
+
}
|
|
51968
|
+
return secp256k1.verify(sig, msgHash, publicKey, {
|
|
51969
|
+
lowS: !allowMalleable
|
|
51970
|
+
});
|
|
51971
|
+
};
|
|
51972
|
+
var isCompactFormat2 = (sig) => {
|
|
51973
|
+
try {
|
|
51974
|
+
const parsed = secp256k1.Signature.fromCompact(sig);
|
|
51975
|
+
return equals3(parsed.toCompactRawBytes(), sig);
|
|
51976
|
+
} catch {
|
|
51977
|
+
return false;
|
|
51978
|
+
}
|
|
51970
51979
|
};
|
|
51971
51980
|
|
|
51972
51981
|
// ../crypto/src/secp256k1/plugin.ts
|
|
@@ -52013,13 +52022,13 @@ var hasPrefix = (bytes3, prefix) => {
|
|
|
52013
52022
|
};
|
|
52014
52023
|
|
|
52015
52024
|
// ../crypto/src/verify.ts
|
|
52016
|
-
var verifySignature = (didKey, data, sig) => {
|
|
52025
|
+
var verifySignature = (didKey, data, sig, opts) => {
|
|
52017
52026
|
const parsed = parseDidKey(didKey);
|
|
52018
52027
|
const plugin = plugins_default.find((p) => p.jwtAlg === parsed.jwtAlg);
|
|
52019
52028
|
if (!plugin) {
|
|
52020
|
-
throw new Error(`Unsupported signature alg:
|
|
52029
|
+
throw new Error(`Unsupported signature alg: ${parsed.jwtAlg}`);
|
|
52021
52030
|
}
|
|
52022
|
-
return plugin.verifySignature(didKey, data, sig);
|
|
52031
|
+
return plugin.verifySignature(didKey, data, sig, opts);
|
|
52023
52032
|
};
|
|
52024
52033
|
|
|
52025
52034
|
// src/auth.ts
|
|
@@ -52064,17 +52073,30 @@ var verifyJwt = async (jwtStr, ownDid, getSigningKey) => {
|
|
|
52064
52073
|
}
|
|
52065
52074
|
const msgBytes = fromString2(parts.slice(0, 2).join("."), "utf8");
|
|
52066
52075
|
const sigBytes = fromString2(sig, "base64url");
|
|
52067
|
-
const
|
|
52076
|
+
const verifySignatureWithKey = (key) => {
|
|
52077
|
+
return verifySignature(key, msgBytes, sigBytes, {
|
|
52078
|
+
allowMalleableSig: true
|
|
52079
|
+
});
|
|
52080
|
+
};
|
|
52081
|
+
const signingKey = await getSigningKey(payload.iss, false);
|
|
52068
52082
|
let validSig;
|
|
52069
52083
|
try {
|
|
52070
|
-
validSig = await
|
|
52084
|
+
validSig = await verifySignatureWithKey(signingKey);
|
|
52071
52085
|
} catch (err) {
|
|
52072
52086
|
throw new AuthRequiredError("could not verify jwt signature", "BadJwtSignature");
|
|
52073
52087
|
}
|
|
52088
|
+
if (!validSig) {
|
|
52089
|
+
const freshSigningKey = await getSigningKey(payload.iss, true);
|
|
52090
|
+
try {
|
|
52091
|
+
validSig = freshSigningKey !== signingKey ? await verifySignatureWithKey(freshSigningKey) : false;
|
|
52092
|
+
} catch (err) {
|
|
52093
|
+
throw new AuthRequiredError("could not verify jwt signature", "BadJwtSignature");
|
|
52094
|
+
}
|
|
52095
|
+
}
|
|
52074
52096
|
if (!validSig) {
|
|
52075
52097
|
throw new AuthRequiredError("jwt signature does not match jwt issuer", "BadJwtSignature");
|
|
52076
52098
|
}
|
|
52077
|
-
return payload
|
|
52099
|
+
return payload;
|
|
52078
52100
|
};
|
|
52079
52101
|
var parseB64UrlToJson = (b64) => {
|
|
52080
52102
|
return JSON.parse(b64UrlToUtf8(b64));
|
|
@@ -52502,7 +52524,7 @@ function decodeQueryParam(type, value) {
|
|
|
52502
52524
|
if (type === "float") {
|
|
52503
52525
|
return Number(String(value));
|
|
52504
52526
|
} else if (type === "integer") {
|
|
52505
|
-
return
|
|
52527
|
+
return parseInt(String(value), 10) || 0;
|
|
52506
52528
|
} else if (type === "boolean") {
|
|
52507
52529
|
return value === "true";
|
|
52508
52530
|
}
|