mirror of
https://github.com/versia-pub/api.git
synced 2025-12-06 08:28:19 +01:00
feat(federation): ✨ Add new signed string output to signer
This commit is contained in:
parent
28e701bc13
commit
8860d09eb4
|
|
@ -20,7 +20,7 @@ describe("SignatureValidator", () => {
|
||||||
|
|
||||||
body = JSON.stringify({ key: "value" });
|
body = JSON.stringify({ key: "value" });
|
||||||
|
|
||||||
const headers = await new SignatureConstructor(
|
const { headers } = await new SignatureConstructor(
|
||||||
privateKey,
|
privateKey,
|
||||||
"https://bob.org/users/6a18f2c3-120e-4949-bda4-2aa4c8264d51",
|
"https://bob.org/users/6a18f2c3-120e-4949-bda4-2aa4c8264d51",
|
||||||
).sign("GET", new URL("https://example.com"), body);
|
).sign("GET", new URL("https://example.com"), body);
|
||||||
|
|
@ -157,7 +157,7 @@ describe("SignatureConstructor", () => {
|
||||||
describe("Signing", () => {
|
describe("Signing", () => {
|
||||||
test("should correctly sign ", async () => {
|
test("should correctly sign ", async () => {
|
||||||
const url = new URL("https://example.com");
|
const url = new URL("https://example.com");
|
||||||
headers = await ctor.sign("GET", url, body);
|
headers = (await ctor.sign("GET", url, body)).headers;
|
||||||
expect(headers.get("Signature")).toBeDefined();
|
expect(headers.get("Signature")).toBeDefined();
|
||||||
expect(headers.get("Date")).toBeDefined();
|
expect(headers.get("Date")).toBeDefined();
|
||||||
|
|
||||||
|
|
@ -187,7 +187,7 @@ describe("SignatureConstructor", () => {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
body: body,
|
body: body,
|
||||||
});
|
});
|
||||||
const newRequest = await ctor.sign(request);
|
const { request: newRequest } = await ctor.sign(request);
|
||||||
|
|
||||||
headers = newRequest.headers;
|
headers = newRequest.headers;
|
||||||
expect(headers.get("Signature")).toBeDefined();
|
expect(headers.get("Signature")).toBeDefined();
|
||||||
|
|
@ -195,5 +195,12 @@ describe("SignatureConstructor", () => {
|
||||||
|
|
||||||
expect(await newRequest.text()).toBe(body);
|
expect(await newRequest.text()).toBe(body);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("signing should also output a signed string", async () => {
|
||||||
|
const url = new URL("https://example.com");
|
||||||
|
const { signedString } = await ctor.sign("GET", url, body);
|
||||||
|
expect(signedString).toBeString();
|
||||||
|
expect(signedString.length).toBeGreaterThan(10);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -231,12 +231,15 @@ export class SignatureConstructor {
|
||||||
/**
|
/**
|
||||||
* Signs a request.
|
* Signs a request.
|
||||||
* @param request The request object to sign.
|
* @param request The request object to sign.
|
||||||
* @returns A Promise that resolves to the signed request.
|
* @returns A Promise that resolves to the signed request, plus the signed string.
|
||||||
* @example
|
* @example
|
||||||
* const request = new Request();
|
* const request = new Request();
|
||||||
* const signedRequest = await constructor.sign(request);
|
* const { request: signedRequest } = await constructor.sign(request);
|
||||||
*/
|
*/
|
||||||
async sign(request: Request): Promise<Request>;
|
async sign(request: Request): Promise<{
|
||||||
|
request: Request;
|
||||||
|
signedString: string;
|
||||||
|
}>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Signs a request.
|
* Signs a request.
|
||||||
|
|
@ -244,41 +247,59 @@ export class SignatureConstructor {
|
||||||
* @param url The URL object.
|
* @param url The URL object.
|
||||||
* @param body The request body.
|
* @param body The request body.
|
||||||
* @param headers The request headers.
|
* @param headers The request headers.
|
||||||
* @returns A Promise that resolves to the signed headers.
|
* @param date The date that the request was signed (optional)
|
||||||
|
* @returns A Promise that resolves to the signed headers, and the signed string.
|
||||||
* @throws TypeError if any required parameters are missing or empty.
|
* @throws TypeError if any required parameters are missing or empty.
|
||||||
* @example
|
* @example
|
||||||
* const method = "GET";
|
* const method = "GET";
|
||||||
* const url = new URL("https://example.com");
|
* const url = new URL("https://example.com");
|
||||||
* const body = "request body";
|
* const body = "request body";
|
||||||
* const signedHeaders = await constructor.sign(method, url, body);
|
* const { headers: signedHeaders } = await constructor.sign(method, url, body);
|
||||||
*/
|
*/
|
||||||
async sign(
|
async sign(
|
||||||
method: HttpVerb,
|
method: HttpVerb,
|
||||||
url: URL,
|
url: URL,
|
||||||
body: string,
|
body: string,
|
||||||
headers?: Headers,
|
headers?: Headers,
|
||||||
): Promise<Headers>;
|
date?: Date,
|
||||||
|
): Promise<{
|
||||||
|
headers: Headers;
|
||||||
|
signedString: string;
|
||||||
|
}>;
|
||||||
|
|
||||||
async sign(
|
async sign(
|
||||||
requestOrMethod: Request | HttpVerb,
|
requestOrMethod: Request | HttpVerb,
|
||||||
url?: URL,
|
url?: URL,
|
||||||
body?: string,
|
body?: string,
|
||||||
headers: Headers = new Headers(),
|
headers: Headers = new Headers(),
|
||||||
): Promise<Request | Headers> {
|
date?: Date,
|
||||||
|
): Promise<
|
||||||
|
| {
|
||||||
|
headers: Headers;
|
||||||
|
signedString: string;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
request: Request;
|
||||||
|
signedString: string;
|
||||||
|
}
|
||||||
|
> {
|
||||||
if (requestOrMethod instanceof Request) {
|
if (requestOrMethod instanceof Request) {
|
||||||
const request = requestOrMethod.clone();
|
const request = requestOrMethod.clone();
|
||||||
|
|
||||||
const headers = await this.sign(
|
const { headers, signedString } = await this.sign(
|
||||||
requestOrMethod.method as HttpVerb,
|
requestOrMethod.method as HttpVerb,
|
||||||
new URL(requestOrMethod.url),
|
new URL(requestOrMethod.url),
|
||||||
await requestOrMethod.text(),
|
await requestOrMethod.text(),
|
||||||
requestOrMethod.headers,
|
requestOrMethod.headers,
|
||||||
|
requestOrMethod.headers.get("Date")
|
||||||
|
? new Date(requestOrMethod.headers.get("Date") ?? "")
|
||||||
|
: undefined,
|
||||||
);
|
);
|
||||||
|
|
||||||
request.headers.set("Date", headers.get("Date") ?? "");
|
request.headers.set("Date", headers.get("Date") ?? "");
|
||||||
request.headers.set("Signature", headers.get("Signature") ?? "");
|
request.headers.set("Signature", headers.get("Signature") ?? "");
|
||||||
|
|
||||||
return request;
|
return { request, signedString };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!url || !body || !headers) {
|
if (!url || !body || !headers) {
|
||||||
|
|
@ -287,38 +308,42 @@ export class SignatureConstructor {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const date = new Date().toISOString();
|
const finalDate = date?.toISOString() ?? new Date().toISOString();
|
||||||
|
|
||||||
const digest = await crypto.subtle.digest(
|
const digest = await crypto.subtle.digest(
|
||||||
"SHA-256",
|
"SHA-256",
|
||||||
new TextEncoder().encode(body),
|
new TextEncoder().encode(body),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const signedString =
|
||||||
|
`(request-target): ${requestOrMethod.toLowerCase()} ${
|
||||||
|
url.pathname
|
||||||
|
}\n` +
|
||||||
|
`host: ${url.host}\n` +
|
||||||
|
`date: ${finalDate}\n` +
|
||||||
|
`digest: SHA-256=${Buffer.from(new Uint8Array(digest)).toString(
|
||||||
|
"base64",
|
||||||
|
)}\n`;
|
||||||
|
|
||||||
const signature = await crypto.subtle.sign(
|
const signature = await crypto.subtle.sign(
|
||||||
"Ed25519",
|
"Ed25519",
|
||||||
this.privateKey,
|
this.privateKey,
|
||||||
new TextEncoder().encode(
|
new TextEncoder().encode(signedString),
|
||||||
`(request-target): ${requestOrMethod.toLowerCase()} ${
|
|
||||||
url.pathname
|
|
||||||
}\n` +
|
|
||||||
`host: ${url.host}\n` +
|
|
||||||
`date: ${date}\n` +
|
|
||||||
`digest: SHA-256=${Buffer.from(
|
|
||||||
new Uint8Array(digest),
|
|
||||||
).toString("base64")}\n`,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const signatureBase64 = Buffer.from(new Uint8Array(signature)).toString(
|
const signatureBase64 = Buffer.from(new Uint8Array(signature)).toString(
|
||||||
"base64",
|
"base64",
|
||||||
);
|
);
|
||||||
|
|
||||||
headers.set("Date", date);
|
headers.set("Date", finalDate);
|
||||||
headers.set(
|
headers.set(
|
||||||
"Signature",
|
"Signature",
|
||||||
`keyId="${this.keyId}",algorithm="ed25519",headers="(request-target) host date digest",signature="${signatureBase64}"`,
|
`keyId="${this.keyId}",algorithm="ed25519",headers="(request-target) host date digest",signature="${signatureBase64}"`,
|
||||||
);
|
);
|
||||||
|
|
||||||
return headers;
|
return {
|
||||||
|
headers,
|
||||||
|
signedString,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue