@reauth-dev/sdk 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.
- package/LICENSE +21 -0
- package/README.md +252 -0
- package/dist/chunk-JX2J36FS.mjs +269 -0
- package/dist/index.d.mts +127 -0
- package/dist/index.d.ts +127 -0
- package/dist/index.js +308 -0
- package/dist/index.mjs +17 -0
- package/dist/react/index.d.mts +123 -0
- package/dist/react/index.d.ts +123 -0
- package/dist/react/index.js +448 -0
- package/dist/react/index.mjs +154 -0
- package/dist/server.d.mts +188 -0
- package/dist/server.d.ts +188 -0
- package/dist/server.js +391 -0
- package/dist/server.mjs +356 -0
- package/dist/types-D8oOYbeC.d.mts +169 -0
- package/dist/types-D8oOYbeC.d.ts +169 -0
- package/dist/webhooks.d.mts +23 -0
- package/dist/webhooks.d.ts +23 -0
- package/dist/webhooks.js +123 -0
- package/dist/webhooks.mjs +94 -0
- package/package.json +78 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
declare const SIGNATURE_HEADER = "reauth-webhook-signature";
|
|
2
|
+
declare const ID_HEADER = "reauth-webhook-id";
|
|
3
|
+
declare const TIMESTAMP_HEADER = "reauth-webhook-timestamp";
|
|
4
|
+
type WebhookEvent = {
|
|
5
|
+
id: string;
|
|
6
|
+
type: string;
|
|
7
|
+
api_version: string;
|
|
8
|
+
created_at: string;
|
|
9
|
+
domain_id: string;
|
|
10
|
+
data: Record<string, unknown>;
|
|
11
|
+
};
|
|
12
|
+
type VerifyWebhookOptions = {
|
|
13
|
+
payload: string;
|
|
14
|
+
signature: string;
|
|
15
|
+
secret: string;
|
|
16
|
+
tolerance?: number;
|
|
17
|
+
};
|
|
18
|
+
declare class WebhookVerificationError extends Error {
|
|
19
|
+
constructor(message: string);
|
|
20
|
+
}
|
|
21
|
+
declare function verifyWebhookSignature(options: VerifyWebhookOptions): WebhookEvent;
|
|
22
|
+
|
|
23
|
+
export { ID_HEADER, SIGNATURE_HEADER, TIMESTAMP_HEADER, type VerifyWebhookOptions, type WebhookEvent, WebhookVerificationError, verifyWebhookSignature };
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
declare const SIGNATURE_HEADER = "reauth-webhook-signature";
|
|
2
|
+
declare const ID_HEADER = "reauth-webhook-id";
|
|
3
|
+
declare const TIMESTAMP_HEADER = "reauth-webhook-timestamp";
|
|
4
|
+
type WebhookEvent = {
|
|
5
|
+
id: string;
|
|
6
|
+
type: string;
|
|
7
|
+
api_version: string;
|
|
8
|
+
created_at: string;
|
|
9
|
+
domain_id: string;
|
|
10
|
+
data: Record<string, unknown>;
|
|
11
|
+
};
|
|
12
|
+
type VerifyWebhookOptions = {
|
|
13
|
+
payload: string;
|
|
14
|
+
signature: string;
|
|
15
|
+
secret: string;
|
|
16
|
+
tolerance?: number;
|
|
17
|
+
};
|
|
18
|
+
declare class WebhookVerificationError extends Error {
|
|
19
|
+
constructor(message: string);
|
|
20
|
+
}
|
|
21
|
+
declare function verifyWebhookSignature(options: VerifyWebhookOptions): WebhookEvent;
|
|
22
|
+
|
|
23
|
+
export { ID_HEADER, SIGNATURE_HEADER, TIMESTAMP_HEADER, type VerifyWebhookOptions, type WebhookEvent, WebhookVerificationError, verifyWebhookSignature };
|
package/dist/webhooks.js
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/webhooks.ts
|
|
21
|
+
var webhooks_exports = {};
|
|
22
|
+
__export(webhooks_exports, {
|
|
23
|
+
ID_HEADER: () => ID_HEADER,
|
|
24
|
+
SIGNATURE_HEADER: () => SIGNATURE_HEADER,
|
|
25
|
+
TIMESTAMP_HEADER: () => TIMESTAMP_HEADER,
|
|
26
|
+
WebhookVerificationError: () => WebhookVerificationError,
|
|
27
|
+
verifyWebhookSignature: () => verifyWebhookSignature
|
|
28
|
+
});
|
|
29
|
+
module.exports = __toCommonJS(webhooks_exports);
|
|
30
|
+
var import_node_crypto = require("crypto");
|
|
31
|
+
var SIGNATURE_HEADER = "reauth-webhook-signature";
|
|
32
|
+
var ID_HEADER = "reauth-webhook-id";
|
|
33
|
+
var TIMESTAMP_HEADER = "reauth-webhook-timestamp";
|
|
34
|
+
var DEFAULT_TOLERANCE_SECONDS = 300;
|
|
35
|
+
var WebhookVerificationError = class extends Error {
|
|
36
|
+
constructor(message) {
|
|
37
|
+
super(message);
|
|
38
|
+
this.name = "WebhookVerificationError";
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
function parseSignatureHeader(header) {
|
|
42
|
+
const parts = header.split(",");
|
|
43
|
+
let timestamp = "";
|
|
44
|
+
const signatures = [];
|
|
45
|
+
for (const part of parts) {
|
|
46
|
+
const [key, value] = part.split("=", 2);
|
|
47
|
+
if (key === "t") {
|
|
48
|
+
timestamp = value;
|
|
49
|
+
} else if (key === "v1") {
|
|
50
|
+
signatures.push(value);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return { timestamp, signatures };
|
|
54
|
+
}
|
|
55
|
+
function isValidHex(value) {
|
|
56
|
+
return /^[0-9a-fA-F]+$/.test(value) && value.length % 2 === 0;
|
|
57
|
+
}
|
|
58
|
+
function computeSignature(secret, timestamp, payload) {
|
|
59
|
+
const signedPayload = `${timestamp}.${payload}`;
|
|
60
|
+
return (0, import_node_crypto.createHmac)("sha256", secret).update(signedPayload).digest("hex");
|
|
61
|
+
}
|
|
62
|
+
function verifyWebhookSignature(options) {
|
|
63
|
+
const { payload, signature, secret, tolerance = DEFAULT_TOLERANCE_SECONDS } = options;
|
|
64
|
+
if (!signature) {
|
|
65
|
+
throw new WebhookVerificationError("Missing webhook signature header");
|
|
66
|
+
}
|
|
67
|
+
if (!secret) {
|
|
68
|
+
throw new WebhookVerificationError("Missing webhook secret");
|
|
69
|
+
}
|
|
70
|
+
const rawSecret = secret.startsWith("whsec_") ? secret.slice(6) : secret;
|
|
71
|
+
const { timestamp, signatures } = parseSignatureHeader(signature);
|
|
72
|
+
if (!timestamp || signatures.length === 0) {
|
|
73
|
+
throw new WebhookVerificationError(
|
|
74
|
+
"Invalid signature header format: expected t=<timestamp>,v1=<signature>"
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
const timestampSeconds = parseInt(timestamp, 10);
|
|
78
|
+
if (isNaN(timestampSeconds)) {
|
|
79
|
+
throw new WebhookVerificationError("Invalid timestamp in signature header");
|
|
80
|
+
}
|
|
81
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
82
|
+
const age = now - timestampSeconds;
|
|
83
|
+
if (age > tolerance) {
|
|
84
|
+
throw new WebhookVerificationError(
|
|
85
|
+
`Webhook timestamp too old: ${age}s exceeds tolerance of ${tolerance}s`
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
if (age < -tolerance) {
|
|
89
|
+
throw new WebhookVerificationError(
|
|
90
|
+
`Webhook timestamp too far in the future: ${-age}s exceeds tolerance of ${tolerance}s`
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
const expectedSignature = computeSignature(rawSecret, timestamp, payload);
|
|
94
|
+
const expectedBuffer = Buffer.from(expectedSignature, "hex");
|
|
95
|
+
let verified = false;
|
|
96
|
+
for (const sig of signatures) {
|
|
97
|
+
if (!isValidHex(sig)) {
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
const sigBuffer = Buffer.from(sig, "hex");
|
|
101
|
+
if (sigBuffer.length === expectedBuffer.length && (0, import_node_crypto.timingSafeEqual)(sigBuffer, expectedBuffer)) {
|
|
102
|
+
verified = true;
|
|
103
|
+
break;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
if (!verified) {
|
|
107
|
+
throw new WebhookVerificationError("Webhook signature verification failed");
|
|
108
|
+
}
|
|
109
|
+
try {
|
|
110
|
+
const event = JSON.parse(payload);
|
|
111
|
+
return event;
|
|
112
|
+
} catch {
|
|
113
|
+
throw new WebhookVerificationError("Failed to parse webhook payload as JSON");
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
117
|
+
0 && (module.exports = {
|
|
118
|
+
ID_HEADER,
|
|
119
|
+
SIGNATURE_HEADER,
|
|
120
|
+
TIMESTAMP_HEADER,
|
|
121
|
+
WebhookVerificationError,
|
|
122
|
+
verifyWebhookSignature
|
|
123
|
+
});
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
// src/webhooks.ts
|
|
2
|
+
import { createHmac, timingSafeEqual } from "crypto";
|
|
3
|
+
var SIGNATURE_HEADER = "reauth-webhook-signature";
|
|
4
|
+
var ID_HEADER = "reauth-webhook-id";
|
|
5
|
+
var TIMESTAMP_HEADER = "reauth-webhook-timestamp";
|
|
6
|
+
var DEFAULT_TOLERANCE_SECONDS = 300;
|
|
7
|
+
var WebhookVerificationError = class extends Error {
|
|
8
|
+
constructor(message) {
|
|
9
|
+
super(message);
|
|
10
|
+
this.name = "WebhookVerificationError";
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
function parseSignatureHeader(header) {
|
|
14
|
+
const parts = header.split(",");
|
|
15
|
+
let timestamp = "";
|
|
16
|
+
const signatures = [];
|
|
17
|
+
for (const part of parts) {
|
|
18
|
+
const [key, value] = part.split("=", 2);
|
|
19
|
+
if (key === "t") {
|
|
20
|
+
timestamp = value;
|
|
21
|
+
} else if (key === "v1") {
|
|
22
|
+
signatures.push(value);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return { timestamp, signatures };
|
|
26
|
+
}
|
|
27
|
+
function isValidHex(value) {
|
|
28
|
+
return /^[0-9a-fA-F]+$/.test(value) && value.length % 2 === 0;
|
|
29
|
+
}
|
|
30
|
+
function computeSignature(secret, timestamp, payload) {
|
|
31
|
+
const signedPayload = `${timestamp}.${payload}`;
|
|
32
|
+
return createHmac("sha256", secret).update(signedPayload).digest("hex");
|
|
33
|
+
}
|
|
34
|
+
function verifyWebhookSignature(options) {
|
|
35
|
+
const { payload, signature, secret, tolerance = DEFAULT_TOLERANCE_SECONDS } = options;
|
|
36
|
+
if (!signature) {
|
|
37
|
+
throw new WebhookVerificationError("Missing webhook signature header");
|
|
38
|
+
}
|
|
39
|
+
if (!secret) {
|
|
40
|
+
throw new WebhookVerificationError("Missing webhook secret");
|
|
41
|
+
}
|
|
42
|
+
const rawSecret = secret.startsWith("whsec_") ? secret.slice(6) : secret;
|
|
43
|
+
const { timestamp, signatures } = parseSignatureHeader(signature);
|
|
44
|
+
if (!timestamp || signatures.length === 0) {
|
|
45
|
+
throw new WebhookVerificationError(
|
|
46
|
+
"Invalid signature header format: expected t=<timestamp>,v1=<signature>"
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
const timestampSeconds = parseInt(timestamp, 10);
|
|
50
|
+
if (isNaN(timestampSeconds)) {
|
|
51
|
+
throw new WebhookVerificationError("Invalid timestamp in signature header");
|
|
52
|
+
}
|
|
53
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
54
|
+
const age = now - timestampSeconds;
|
|
55
|
+
if (age > tolerance) {
|
|
56
|
+
throw new WebhookVerificationError(
|
|
57
|
+
`Webhook timestamp too old: ${age}s exceeds tolerance of ${tolerance}s`
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
if (age < -tolerance) {
|
|
61
|
+
throw new WebhookVerificationError(
|
|
62
|
+
`Webhook timestamp too far in the future: ${-age}s exceeds tolerance of ${tolerance}s`
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
const expectedSignature = computeSignature(rawSecret, timestamp, payload);
|
|
66
|
+
const expectedBuffer = Buffer.from(expectedSignature, "hex");
|
|
67
|
+
let verified = false;
|
|
68
|
+
for (const sig of signatures) {
|
|
69
|
+
if (!isValidHex(sig)) {
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
const sigBuffer = Buffer.from(sig, "hex");
|
|
73
|
+
if (sigBuffer.length === expectedBuffer.length && timingSafeEqual(sigBuffer, expectedBuffer)) {
|
|
74
|
+
verified = true;
|
|
75
|
+
break;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
if (!verified) {
|
|
79
|
+
throw new WebhookVerificationError("Webhook signature verification failed");
|
|
80
|
+
}
|
|
81
|
+
try {
|
|
82
|
+
const event = JSON.parse(payload);
|
|
83
|
+
return event;
|
|
84
|
+
} catch {
|
|
85
|
+
throw new WebhookVerificationError("Failed to parse webhook payload as JSON");
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
export {
|
|
89
|
+
ID_HEADER,
|
|
90
|
+
SIGNATURE_HEADER,
|
|
91
|
+
TIMESTAMP_HEADER,
|
|
92
|
+
WebhookVerificationError,
|
|
93
|
+
verifyWebhookSignature
|
|
94
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@reauth-dev/sdk",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "TypeScript SDK for reauth.dev authentication",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"module": "dist/index.mjs",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.mjs",
|
|
12
|
+
"require": "./dist/index.js"
|
|
13
|
+
},
|
|
14
|
+
"./react": {
|
|
15
|
+
"types": "./dist/react/index.d.ts",
|
|
16
|
+
"import": "./dist/react/index.mjs",
|
|
17
|
+
"require": "./dist/react/index.js"
|
|
18
|
+
},
|
|
19
|
+
"./server": {
|
|
20
|
+
"types": "./dist/server.d.ts",
|
|
21
|
+
"import": "./dist/server.mjs",
|
|
22
|
+
"require": "./dist/server.js"
|
|
23
|
+
},
|
|
24
|
+
"./webhooks": {
|
|
25
|
+
"types": "./dist/webhooks.d.ts",
|
|
26
|
+
"import": "./dist/webhooks.mjs",
|
|
27
|
+
"require": "./dist/webhooks.js"
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
"files": [
|
|
31
|
+
"dist",
|
|
32
|
+
"README.md",
|
|
33
|
+
"LICENSE"
|
|
34
|
+
],
|
|
35
|
+
"publishConfig": {
|
|
36
|
+
"access": "public"
|
|
37
|
+
},
|
|
38
|
+
"scripts": {
|
|
39
|
+
"build": "tsup",
|
|
40
|
+
"dev": "tsup --watch",
|
|
41
|
+
"typecheck": "tsc --noEmit",
|
|
42
|
+
"format": "prettier --write src"
|
|
43
|
+
},
|
|
44
|
+
"dependencies": {
|
|
45
|
+
"jose": "^5.0.0"
|
|
46
|
+
},
|
|
47
|
+
"peerDependencies": {
|
|
48
|
+
"react": ">=18.0.0"
|
|
49
|
+
},
|
|
50
|
+
"peerDependenciesMeta": {
|
|
51
|
+
"react": {
|
|
52
|
+
"optional": true
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
"devDependencies": {
|
|
56
|
+
"@types/node": "^20.0.0",
|
|
57
|
+
"@types/react": "^18.0.0",
|
|
58
|
+
"prettier": "^3.8.1",
|
|
59
|
+
"react": "^18.0.0",
|
|
60
|
+
"tsup": "^8.0.0",
|
|
61
|
+
"typescript": "^5.0.0"
|
|
62
|
+
},
|
|
63
|
+
"keywords": [
|
|
64
|
+
"auth",
|
|
65
|
+
"authentication",
|
|
66
|
+
"reauth",
|
|
67
|
+
"magic-link",
|
|
68
|
+
"passwordless"
|
|
69
|
+
],
|
|
70
|
+
"license": "MIT",
|
|
71
|
+
"repository": {
|
|
72
|
+
"type": "git",
|
|
73
|
+
"url": "https://github.com/sssemil/reauth-sdk.git",
|
|
74
|
+
"directory": "ts"
|
|
75
|
+
},
|
|
76
|
+
"homepage": "https://github.com/sssemil/reauth-sdk/tree/main/ts#readme",
|
|
77
|
+
"bugs": "https://github.com/sssemil/reauth-sdk/issues"
|
|
78
|
+
}
|