secure-web-token 1.0.1 → 1.0.3
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/README.md +82 -112
- package/dist/decrypt.d.ts +7 -0
- package/dist/decrypt.d.ts.map +1 -1
- package/dist/decrypt.js +7 -0
- package/dist/device.d.ts +8 -0
- package/dist/device.d.ts.map +1 -0
- package/dist/device.js +46 -0
- package/dist/encrypt.d.ts +8 -0
- package/dist/encrypt.d.ts.map +1 -1
- package/dist/encrypt.js +8 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/sign.d.ts +10 -15
- package/dist/sign.d.ts.map +1 -1
- package/dist/sign.js +30 -19
- package/dist/utils.d.ts +28 -0
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +28 -0
- package/dist/verify.d.ts +10 -1
- package/dist/verify.d.ts.map +1 -1
- package/dist/verify.js +13 -8
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,178 +1,148 @@
|
|
|
1
|
-
# 🔐 Secure Web Token (
|
|
1
|
+
# 🔐 Secure Web Token (SWT)
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
## 1. About the Package
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
**Secure Web Token (SWT)** is a Node.js authentication package that provides a more secure alternative to JWT by **encrypting token payloads** and **binding tokens to specific devices** using fingerprints.
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
## ✨ Features
|
|
10
|
-
|
|
11
|
-
- Supports **single or multiple fingerprints**
|
|
12
|
-
- Simple `sign()` and `verify()` API
|
|
13
|
-
- CommonJS compatible
|
|
14
|
-
- Editor IntelliSense support
|
|
15
|
-
- No heavy dependencies
|
|
7
|
+
It is designed for applications where security and device-level access control matter.
|
|
16
8
|
|
|
17
9
|
---
|
|
18
10
|
|
|
19
|
-
##
|
|
11
|
+
## 2. What Problem Does It Solve?
|
|
20
12
|
|
|
21
|
-
|
|
22
|
-
npm install secure-web-token
|
|
23
|
-
```
|
|
13
|
+
Traditional JWT has some well-known issues:
|
|
24
14
|
|
|
25
|
-
|
|
15
|
+
- JWT payloads are **Base64 encoded**, not encrypted
|
|
16
|
+
- Anyone can decode the payload using online tools without the secret
|
|
17
|
+
- If a token leaks, it can be reused on **any device**
|
|
18
|
+
- No built-in way to restrict tokens to a specific device
|
|
26
19
|
|
|
27
|
-
|
|
20
|
+
**Secure Web Token (SWT)** solves these problems by:
|
|
28
21
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
22
|
+
- Encrypting the payload using **AES-256-GCM**
|
|
23
|
+
- Making payload data **completely unreadable without the secret**
|
|
24
|
+
- Allowing tokens to be bound to **one or more device fingerprints**
|
|
25
|
+
- Preventing token reuse from unauthorized devices
|
|
26
|
+
- Supporting auto-generated device IDs for stronger protection
|
|
34
27
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
-
|
|
39
|
-
- Device
|
|
40
|
-
- IP address
|
|
41
|
-
- Custom string
|
|
42
|
-
|
|
43
|
-
You can pass **one or multiple fingerprints**.
|
|
28
|
+
This makes SWT especially useful for:
|
|
29
|
+
- Course platforms (anti-piracy)
|
|
30
|
+
- SaaS dashboards
|
|
31
|
+
- Admin panels
|
|
32
|
+
- Device-restricted systems
|
|
44
33
|
|
|
45
34
|
---
|
|
46
35
|
|
|
47
|
-
##
|
|
48
|
-
|
|
49
|
-
Creates a secure token with fingerprint validation.
|
|
36
|
+
## 3. Available Functions
|
|
50
37
|
|
|
51
|
-
###
|
|
38
|
+
### `sign()`
|
|
39
|
+
Creates an encrypted and signed token.
|
|
52
40
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
| Parameter | Type | Description |
|
|
60
|
-
|---------|-----|-------------|
|
|
61
|
-
| payload | Object | Data you want inside token |
|
|
62
|
-
| secret | String | Secret key |
|
|
63
|
-
| options.expiresIn | Number | Expiry time (seconds) |
|
|
64
|
-
| options.fingerprint | String \| String[] | Allowed fingerprint(s) |
|
|
41
|
+
**Features:**
|
|
42
|
+
- Encrypts payload
|
|
43
|
+
- Adds expiry (`iat`, `exp`)
|
|
44
|
+
- Supports device fingerprint binding
|
|
45
|
+
- Can auto-generate a device ID
|
|
65
46
|
|
|
66
47
|
---
|
|
67
48
|
|
|
68
|
-
###
|
|
49
|
+
### `verify()`
|
|
50
|
+
Verifies and decrypts a token.
|
|
69
51
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
expiresIn: 60,
|
|
76
|
-
fingerprint: "Chrome-Linux"
|
|
77
|
-
}
|
|
78
|
-
);
|
|
79
|
-
|
|
80
|
-
console.log("TOKEN:", token);
|
|
81
|
-
```
|
|
52
|
+
**Checks performed:**
|
|
53
|
+
- Token format
|
|
54
|
+
- Signature integrity
|
|
55
|
+
- Token expiry
|
|
56
|
+
- Device fingerprint validation
|
|
82
57
|
|
|
83
58
|
---
|
|
84
59
|
|
|
85
|
-
|
|
60
|
+
## 4. Sample Code
|
|
86
61
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
{
|
|
92
|
-
expiresIn: 60,
|
|
93
|
-
fingerprint: ["Chrome-Linux", "Windows"]
|
|
94
|
-
}
|
|
95
|
-
);
|
|
62
|
+
### Installation
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
npm install secure-web-token
|
|
96
66
|
```
|
|
97
67
|
|
|
98
68
|
---
|
|
99
69
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
Verifies the token and checks whether the fingerprint is valid.
|
|
103
|
-
|
|
104
|
-
### Syntax
|
|
70
|
+
### Import
|
|
105
71
|
|
|
106
72
|
```js
|
|
107
|
-
verify(token
|
|
73
|
+
const { sign, verify } = require("secure-web-token");
|
|
108
74
|
```
|
|
109
75
|
|
|
110
|
-
### Parameters
|
|
111
|
-
|
|
112
|
-
| Parameter | Type | Description |
|
|
113
|
-
|---------|-----|-------------|
|
|
114
|
-
| token | String | Token to verify |
|
|
115
|
-
| secret | String | Same secret used while signing |
|
|
116
|
-
| options.fingerprint | String \| String[] | Current device fingerprint |
|
|
117
|
-
|
|
118
76
|
---
|
|
119
77
|
|
|
120
|
-
###
|
|
78
|
+
### Signing a Token (Auto Device Registration)
|
|
121
79
|
|
|
122
80
|
```js
|
|
123
|
-
const
|
|
124
|
-
fingerprint: "Chrome-Linux"
|
|
125
|
-
});
|
|
81
|
+
const secret = "my-super-secret";
|
|
126
82
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
### Example: Multiple Fingerprints Verification
|
|
83
|
+
const { token, deviceId } = sign(
|
|
84
|
+
{ userId: 1, role: "admin" },
|
|
85
|
+
secret,
|
|
86
|
+
{ fingerprint: true }
|
|
87
|
+
);
|
|
133
88
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
fingerprint: ["Chrome-Linux", "monjal"]
|
|
137
|
-
});
|
|
89
|
+
console.log("TOKEN:", token);
|
|
90
|
+
console.log("DEVICE ID:", deviceId);
|
|
138
91
|
```
|
|
139
92
|
|
|
140
93
|
---
|
|
141
94
|
|
|
142
|
-
|
|
95
|
+
### Verifying the Token
|
|
143
96
|
|
|
144
97
|
```js
|
|
145
98
|
try {
|
|
146
|
-
verify(token,
|
|
147
|
-
fingerprint:
|
|
99
|
+
const payload = verify(token, secret, {
|
|
100
|
+
fingerprint: deviceId
|
|
148
101
|
});
|
|
102
|
+
|
|
103
|
+
console.log("USER DATA:", payload.data);
|
|
149
104
|
} catch (err) {
|
|
150
|
-
console.error(err.message);
|
|
105
|
+
console.error("AUTH ERROR:", err.message);
|
|
151
106
|
}
|
|
152
107
|
```
|
|
153
108
|
|
|
154
109
|
---
|
|
155
110
|
|
|
156
|
-
|
|
111
|
+
### Using Custom Fingerprints
|
|
157
112
|
|
|
158
113
|
```js
|
|
159
|
-
const token = sign(
|
|
160
|
-
{
|
|
161
|
-
|
|
114
|
+
const { token } = sign(
|
|
115
|
+
{ userId: 2 },
|
|
116
|
+
secret,
|
|
162
117
|
{
|
|
163
|
-
expiresIn:
|
|
164
|
-
fingerprint: "Chrome-Linux"
|
|
118
|
+
expiresIn: 60,
|
|
119
|
+
fingerprint: ["Chrome-Linux", "192.168.1.10"]
|
|
165
120
|
}
|
|
166
121
|
);
|
|
167
122
|
|
|
168
|
-
|
|
169
|
-
verify(token, "secret-key", {
|
|
123
|
+
verify(token, secret, {
|
|
170
124
|
fingerprint: "Chrome-Linux"
|
|
171
125
|
});
|
|
172
126
|
```
|
|
173
127
|
|
|
174
128
|
---
|
|
175
129
|
|
|
176
|
-
##
|
|
130
|
+
## Payload Structure (Internal)
|
|
131
|
+
|
|
132
|
+
```js
|
|
133
|
+
{
|
|
134
|
+
data: {
|
|
135
|
+
userId: 1,
|
|
136
|
+
role: "admin"
|
|
137
|
+
},
|
|
138
|
+
iat: 1768368114,
|
|
139
|
+
exp: 1768369014,
|
|
140
|
+
fp: ["device-id"]
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
## License
|
|
177
147
|
|
|
178
148
|
MIT License
|
package/dist/decrypt.d.ts
CHANGED
|
@@ -1,2 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Decrypts an encrypted SWT payload.
|
|
3
|
+
*
|
|
4
|
+
* @param encryptedPayload - Encrypted Base64URL payload
|
|
5
|
+
* @param secret - Secret key
|
|
6
|
+
* @returns Decrypted payload object
|
|
7
|
+
*/
|
|
1
8
|
export default function decrypt(encryptedPayload: string, secret: string): Record<string, any>;
|
|
2
9
|
//# sourceMappingURL=decrypt.d.ts.map
|
package/dist/decrypt.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"decrypt.d.ts","sourceRoot":"","sources":["../src/decrypt.ts"],"names":[],"mappings":"AAGA,MAAM,CAAC,OAAO,UAAU,OAAO,
|
|
1
|
+
{"version":3,"file":"decrypt.d.ts","sourceRoot":"","sources":["../src/decrypt.ts"],"names":[],"mappings":"AAGA;;;;;;GAMG;AACH,MAAM,CAAC,OAAO,UAAU,OAAO,CAC7B,gBAAgB,EAAE,MAAM,EACxB,MAAM,EAAE,MAAM,GACb,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAqBrB"}
|
package/dist/decrypt.js
CHANGED
|
@@ -36,6 +36,13 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
36
36
|
exports.default = decrypt;
|
|
37
37
|
const crypto = __importStar(require("crypto"));
|
|
38
38
|
const utils_1 = require("./utils");
|
|
39
|
+
/**
|
|
40
|
+
* Decrypts an encrypted SWT payload.
|
|
41
|
+
*
|
|
42
|
+
* @param encryptedPayload - Encrypted Base64URL payload
|
|
43
|
+
* @param secret - Secret key
|
|
44
|
+
* @returns Decrypted payload object
|
|
45
|
+
*/
|
|
39
46
|
function decrypt(encryptedPayload, secret) {
|
|
40
47
|
const data = (0, utils_1.base64urlDecode)(encryptedPayload);
|
|
41
48
|
const iv = data.subarray(0, 12);
|
package/dist/device.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"device.d.ts","sourceRoot":"","sources":["../src/device.ts"],"names":[],"mappings":"AAEA;;;;;GAKG;AACH,wBAAgB,gBAAgB,IAAI,MAAM,CAEzC"}
|
package/dist/device.js
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.generateDeviceId = generateDeviceId;
|
|
37
|
+
const crypto = __importStar(require("crypto"));
|
|
38
|
+
/**
|
|
39
|
+
* Generate a secure, random device ID.
|
|
40
|
+
* Used for Device Registration Model.
|
|
41
|
+
*
|
|
42
|
+
* @returns UUID v4 string
|
|
43
|
+
*/
|
|
44
|
+
function generateDeviceId() {
|
|
45
|
+
return crypto.randomUUID();
|
|
46
|
+
}
|
package/dist/encrypt.d.ts
CHANGED
|
@@ -1,2 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Encrypts payload using AES-256-GCM.
|
|
3
|
+
* Payload is fully encrypted (unlike JWT).
|
|
4
|
+
*
|
|
5
|
+
* @param payload - Object to encrypt
|
|
6
|
+
* @param secret - Secret key
|
|
7
|
+
* @returns Encrypted Base64URL string
|
|
8
|
+
*/
|
|
1
9
|
export default function encrypt(payload: Record<string, any>, secret: string): string;
|
|
2
10
|
//# sourceMappingURL=encrypt.d.ts.map
|
package/dist/encrypt.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"encrypt.d.ts","sourceRoot":"","sources":["../src/encrypt.ts"],"names":[],"mappings":"AAGA,MAAM,CAAC,OAAO,UAAU,OAAO,
|
|
1
|
+
{"version":3,"file":"encrypt.d.ts","sourceRoot":"","sources":["../src/encrypt.ts"],"names":[],"mappings":"AAGA;;;;;;;GAOG;AACH,MAAM,CAAC,OAAO,UAAU,OAAO,CAC7B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC5B,MAAM,EAAE,MAAM,GACb,MAAM,CAkBR"}
|
package/dist/encrypt.js
CHANGED
|
@@ -36,6 +36,14 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
36
36
|
exports.default = encrypt;
|
|
37
37
|
const crypto = __importStar(require("crypto"));
|
|
38
38
|
const utils_1 = require("./utils");
|
|
39
|
+
/**
|
|
40
|
+
* Encrypts payload using AES-256-GCM.
|
|
41
|
+
* Payload is fully encrypted (unlike JWT).
|
|
42
|
+
*
|
|
43
|
+
* @param payload - Object to encrypt
|
|
44
|
+
* @param secret - Secret key
|
|
45
|
+
* @returns Encrypted Base64URL string
|
|
46
|
+
*/
|
|
39
47
|
function encrypt(payload, secret) {
|
|
40
48
|
const iv = crypto.randomBytes(12);
|
|
41
49
|
const key = crypto
|
package/dist/index.d.ts
CHANGED
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,IAAI,EAAE,MAAM,QAAQ,CAAC;AACzC,OAAO,EAAE,OAAO,IAAI,MAAM,EAAE,MAAM,UAAU,CAAC;AAE7C,YAAY,EAAE,WAAW,EAAE,MAAM,QAAQ,CAAC;AAC1C,YAAY,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,OAAO,IAAI,IAAI,EAAE,MAAM,QAAQ,CAAC;AACzC,OAAO,EAAE,OAAO,IAAI,MAAM,EAAE,MAAM,UAAU,CAAC;AAE7C,YAAY,EAAE,WAAW,EAAE,MAAM,QAAQ,CAAC;AAC1C,YAAY,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC"}
|
package/dist/index.js
CHANGED
package/dist/sign.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Options for signing a
|
|
2
|
+
* Options for signing a Secure Web Token (SWT)
|
|
3
3
|
*/
|
|
4
4
|
export interface SignOptions {
|
|
5
5
|
/**
|
|
@@ -8,22 +8,17 @@ export interface SignOptions {
|
|
|
8
8
|
*/
|
|
9
9
|
expiresIn?: number;
|
|
10
10
|
/**
|
|
11
|
-
*
|
|
12
|
-
*
|
|
11
|
+
* true → auto-generate device ID
|
|
12
|
+
* string | string[] → custom fingerprints
|
|
13
13
|
*/
|
|
14
|
-
fingerprint?: string | string[];
|
|
14
|
+
fingerprint?: true | string | string[];
|
|
15
15
|
}
|
|
16
16
|
/**
|
|
17
|
-
* Creates a
|
|
18
|
-
*
|
|
19
|
-
* @param payload - Data you want to store inside the token
|
|
20
|
-
* @param secret - Secret key used for encryption & signature
|
|
21
|
-
* @param options - Optional token settings (expiry, fingerprint)
|
|
22
|
-
*
|
|
23
|
-
* @returns Encrypted and signed token string
|
|
24
|
-
*
|
|
25
|
-
* @example
|
|
26
|
-
* sign({ userId: 1 }, "secret", { expiresIn: 60 })
|
|
17
|
+
* Creates a Secure Web Token (SWT).
|
|
18
|
+
* User data is stored inside `payload.data`.
|
|
27
19
|
*/
|
|
28
|
-
export default function sign(
|
|
20
|
+
export default function sign(data: Record<string, any>, secret: string, options?: SignOptions): {
|
|
21
|
+
token: string;
|
|
22
|
+
deviceId?: string;
|
|
23
|
+
};
|
|
29
24
|
//# sourceMappingURL=sign.d.ts.map
|
package/dist/sign.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sign.d.ts","sourceRoot":"","sources":["../src/sign.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"sign.d.ts","sourceRoot":"","sources":["../src/sign.ts"],"names":[],"mappings":"AAKA;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB;;;OAGG;IACH,WAAW,CAAC,EAAE,IAAI,GAAG,MAAM,GAAG,MAAM,EAAE,CAAC;CACxC;AAED;;;GAGG;AACH,MAAM,CAAC,OAAO,UAAU,IAAI,CAC1B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EACzB,MAAM,EAAE,MAAM,EACd,OAAO,GAAE,WAAgB,GACxB;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,CAgDtC"}
|
package/dist/sign.js
CHANGED
|
@@ -40,31 +40,39 @@ exports.default = sign;
|
|
|
40
40
|
const crypto = __importStar(require("crypto"));
|
|
41
41
|
const encrypt_1 = __importDefault(require("./encrypt"));
|
|
42
42
|
const utils_1 = require("./utils");
|
|
43
|
+
const device_1 = require("./device");
|
|
43
44
|
/**
|
|
44
|
-
* Creates a
|
|
45
|
-
*
|
|
46
|
-
* @param payload - Data you want to store inside the token
|
|
47
|
-
* @param secret - Secret key used for encryption & signature
|
|
48
|
-
* @param options - Optional token settings (expiry, fingerprint)
|
|
49
|
-
*
|
|
50
|
-
* @returns Encrypted and signed token string
|
|
51
|
-
*
|
|
52
|
-
* @example
|
|
53
|
-
* sign({ userId: 1 }, "secret", { expiresIn: 60 })
|
|
45
|
+
* Creates a Secure Web Token (SWT).
|
|
46
|
+
* User data is stored inside `payload.data`.
|
|
54
47
|
*/
|
|
55
|
-
function sign(
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
48
|
+
function sign(data, secret, options = {}) {
|
|
49
|
+
if (!secret || typeof secret !== "string") {
|
|
50
|
+
throw new Error("Secret must be a non-empty string");
|
|
51
|
+
}
|
|
52
|
+
if (!data || typeof data !== "object") {
|
|
53
|
+
throw new Error("Data must be an object");
|
|
54
|
+
}
|
|
60
55
|
const now = Math.floor(Date.now() / 1000);
|
|
61
|
-
payload
|
|
62
|
-
|
|
63
|
-
|
|
56
|
+
const payload = {
|
|
57
|
+
data, // 👈 user data lives here
|
|
58
|
+
iat: now,
|
|
59
|
+
exp: now + (options.expiresIn ?? 900),
|
|
60
|
+
};
|
|
61
|
+
let deviceId;
|
|
62
|
+
// 🔐 Device Registration Model
|
|
63
|
+
if (options.fingerprint === true) {
|
|
64
|
+
deviceId = (0, device_1.generateDeviceId)();
|
|
65
|
+
payload.fp = [deviceId];
|
|
66
|
+
}
|
|
67
|
+
else if (options.fingerprint) {
|
|
64
68
|
payload.fp = Array.isArray(options.fingerprint)
|
|
65
69
|
? options.fingerprint
|
|
66
70
|
: [options.fingerprint];
|
|
67
71
|
}
|
|
72
|
+
const header = {
|
|
73
|
+
alg: "AES-256-GCM+HMAC",
|
|
74
|
+
typ: "SWT",
|
|
75
|
+
};
|
|
68
76
|
const encodedHeader = (0, utils_1.base64urlEncode)(JSON.stringify(header));
|
|
69
77
|
const encryptedPayload = (0, encrypt_1.default)(payload, secret);
|
|
70
78
|
const dataToSign = `${encodedHeader}.${encryptedPayload}`;
|
|
@@ -72,5 +80,8 @@ function sign(payload, secret, options = {}) {
|
|
|
72
80
|
.createHmac("sha256", secret)
|
|
73
81
|
.update(dataToSign)
|
|
74
82
|
.digest("base64url");
|
|
75
|
-
return
|
|
83
|
+
return {
|
|
84
|
+
token: `${dataToSign}.${signature}`,
|
|
85
|
+
deviceId,
|
|
86
|
+
};
|
|
76
87
|
}
|
package/dist/utils.d.ts
CHANGED
|
@@ -1,5 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Encode data into Base64URL format.
|
|
3
|
+
* Used to make tokens URL-safe.
|
|
4
|
+
*
|
|
5
|
+
* @param input - Buffer or string to encode
|
|
6
|
+
* @returns Base64URL encoded string
|
|
7
|
+
*/
|
|
1
8
|
export declare function base64urlEncode(input: Buffer | string): string;
|
|
9
|
+
/**
|
|
10
|
+
* Decode Base64URL encoded data back into Buffer.
|
|
11
|
+
*
|
|
12
|
+
* @param input - Base64URL string
|
|
13
|
+
* @returns Decoded Buffer
|
|
14
|
+
*/
|
|
2
15
|
export declare function base64urlDecode(input: string): Buffer;
|
|
16
|
+
/**
|
|
17
|
+
* Create a SHA-256 hash.
|
|
18
|
+
* Useful for hashing fingerprints or secrets.
|
|
19
|
+
*
|
|
20
|
+
* @param data - String or Buffer to hash
|
|
21
|
+
* @returns SHA-256 hash Buffer
|
|
22
|
+
*/
|
|
3
23
|
export declare function hash(data: string | Buffer): Buffer;
|
|
24
|
+
/**
|
|
25
|
+
* Compare two buffers in constant time.
|
|
26
|
+
* Prevents timing attacks.
|
|
27
|
+
*
|
|
28
|
+
* @param a - First buffer
|
|
29
|
+
* @param b - Second buffer
|
|
30
|
+
* @returns True if equal, false otherwise
|
|
31
|
+
*/
|
|
4
32
|
export declare function timingSafeEqual(a: Buffer, b: Buffer): boolean;
|
|
5
33
|
//# sourceMappingURL=utils.d.ts.map
|
package/dist/utils.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAEA,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAE9D;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAErD;AAED,wBAAgB,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAElD;AAED,wBAAgB,eAAe,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CAG7D"}
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAEA;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAE9D;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAErD;AAED;;;;;;GAMG;AACH,wBAAgB,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAElD;AAED;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CAG7D"}
|
package/dist/utils.js
CHANGED
|
@@ -38,15 +38,43 @@ exports.base64urlDecode = base64urlDecode;
|
|
|
38
38
|
exports.hash = hash;
|
|
39
39
|
exports.timingSafeEqual = timingSafeEqual;
|
|
40
40
|
const crypto = __importStar(require("crypto"));
|
|
41
|
+
/**
|
|
42
|
+
* Encode data into Base64URL format.
|
|
43
|
+
* Used to make tokens URL-safe.
|
|
44
|
+
*
|
|
45
|
+
* @param input - Buffer or string to encode
|
|
46
|
+
* @returns Base64URL encoded string
|
|
47
|
+
*/
|
|
41
48
|
function base64urlEncode(input) {
|
|
42
49
|
return Buffer.from(input).toString("base64url");
|
|
43
50
|
}
|
|
51
|
+
/**
|
|
52
|
+
* Decode Base64URL encoded data back into Buffer.
|
|
53
|
+
*
|
|
54
|
+
* @param input - Base64URL string
|
|
55
|
+
* @returns Decoded Buffer
|
|
56
|
+
*/
|
|
44
57
|
function base64urlDecode(input) {
|
|
45
58
|
return Buffer.from(input, "base64url");
|
|
46
59
|
}
|
|
60
|
+
/**
|
|
61
|
+
* Create a SHA-256 hash.
|
|
62
|
+
* Useful for hashing fingerprints or secrets.
|
|
63
|
+
*
|
|
64
|
+
* @param data - String or Buffer to hash
|
|
65
|
+
* @returns SHA-256 hash Buffer
|
|
66
|
+
*/
|
|
47
67
|
function hash(data) {
|
|
48
68
|
return crypto.createHash("sha256").update(data).digest();
|
|
49
69
|
}
|
|
70
|
+
/**
|
|
71
|
+
* Compare two buffers in constant time.
|
|
72
|
+
* Prevents timing attacks.
|
|
73
|
+
*
|
|
74
|
+
* @param a - First buffer
|
|
75
|
+
* @param b - Second buffer
|
|
76
|
+
* @returns True if equal, false otherwise
|
|
77
|
+
*/
|
|
50
78
|
function timingSafeEqual(a, b) {
|
|
51
79
|
if (a.length !== b.length)
|
|
52
80
|
return false;
|
package/dist/verify.d.ts
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Options for verifying a Secure Web Token
|
|
3
|
+
*/
|
|
1
4
|
export interface VerifyOptions {
|
|
2
|
-
|
|
5
|
+
/**
|
|
6
|
+
* Device fingerprint(s) allowed to verify token
|
|
7
|
+
*/
|
|
8
|
+
fingerprint?: string;
|
|
3
9
|
}
|
|
10
|
+
/**
|
|
11
|
+
* Verifies and decrypts a Secure Web Token.
|
|
12
|
+
*/
|
|
4
13
|
export default function verify(token: string, secret: string, options?: VerifyOptions): Record<string, any>;
|
|
5
14
|
//# sourceMappingURL=verify.d.ts.map
|
package/dist/verify.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"verify.d.ts","sourceRoot":"","sources":["../src/verify.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,aAAa;
|
|
1
|
+
{"version":3,"file":"verify.d.ts","sourceRoot":"","sources":["../src/verify.ts"],"names":[],"mappings":"AAIA;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;GAEG;AACH,MAAM,CAAC,OAAO,UAAU,MAAM,CAC5B,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EACd,OAAO,GAAE,aAAkB,GAC1B,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAmDrB"}
|
package/dist/verify.js
CHANGED
|
@@ -40,7 +40,13 @@ exports.default = verify;
|
|
|
40
40
|
const crypto = __importStar(require("crypto"));
|
|
41
41
|
const decrypt_1 = __importDefault(require("./decrypt"));
|
|
42
42
|
const utils_1 = require("./utils");
|
|
43
|
+
/**
|
|
44
|
+
* Verifies and decrypts a Secure Web Token.
|
|
45
|
+
*/
|
|
43
46
|
function verify(token, secret, options = {}) {
|
|
47
|
+
if (!token || typeof token !== "string") {
|
|
48
|
+
throw new Error("Token must be a string");
|
|
49
|
+
}
|
|
44
50
|
const parts = token.split(".");
|
|
45
51
|
if (parts.length !== 3) {
|
|
46
52
|
throw new Error("Invalid token format");
|
|
@@ -59,17 +65,16 @@ function verify(token, secret, options = {}) {
|
|
|
59
65
|
if (payload.exp < now) {
|
|
60
66
|
throw new Error("Token expired");
|
|
61
67
|
}
|
|
68
|
+
if (!payload.data || typeof payload.data !== "object") {
|
|
69
|
+
throw new Error("Invalid payload structure");
|
|
70
|
+
}
|
|
62
71
|
if (options.fingerprint) {
|
|
63
|
-
const provided =
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
const stored = Array.isArray(payload.fp)
|
|
67
|
-
? payload.fp
|
|
68
|
-
: [];
|
|
69
|
-
const matched = provided.some(fp => stored.includes(fp));
|
|
72
|
+
const provided = options.fingerprint;
|
|
73
|
+
const stored = payload.fp;
|
|
74
|
+
const matched = provided === stored;
|
|
70
75
|
if (!matched) {
|
|
71
76
|
throw new Error("Fingerprint mismatch");
|
|
72
77
|
}
|
|
73
78
|
}
|
|
74
|
-
return payload;
|
|
79
|
+
return payload; // { data, iat, exp, fp }
|
|
75
80
|
}
|