@archlast/shared 0.0.1 → 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/README.md +264 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,10 +1,270 @@
|
|
|
1
1
|
# @archlast/shared
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Internal shared utilities for Archlast packages. This package provides token obfuscation, validation helpers, and shared constants used across the Archlast ecosystem.
|
|
4
4
|
|
|
5
|
-
This package is primarily
|
|
6
|
-
`@archlast/client`, `@archlast/server`, or `@archlast/cli` instead.
|
|
5
|
+
> ⚠️ **Internal Package**: Most applications should use `@archlast/client`, `@archlast/server`, or `@archlast/cli` instead. This package is primarily for internal use by other Archlast packages.
|
|
7
6
|
|
|
8
|
-
##
|
|
7
|
+
## Table of Contents
|
|
8
|
+
|
|
9
|
+
- [Installation](#installation)
|
|
10
|
+
- [Features](#features)
|
|
11
|
+
- [Token Obfuscation](#token-obfuscation)
|
|
12
|
+
- [Token Format](#token-format)
|
|
13
|
+
- [API Reference](#api-reference)
|
|
14
|
+
- [Configuration](#configuration)
|
|
15
|
+
- [Security Considerations](#security-considerations)
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install @archlast/shared
|
|
21
|
+
|
|
22
|
+
# Or with other package managers
|
|
23
|
+
pnpm add @archlast/shared
|
|
24
|
+
bun add @archlast/shared
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Features
|
|
28
|
+
|
|
29
|
+
- **Token Obfuscation**: Secure token wrapping with HMAC signatures
|
|
30
|
+
- **Token Validation**: Verify and extract obfuscated tokens
|
|
31
|
+
- **Path-Bound Tokens**: Tokens are bound to specific API paths
|
|
32
|
+
- **Timestamp Validation**: Short validity window prevents replay attacks
|
|
33
|
+
- **Shared Constants**: Common constants used across packages
|
|
34
|
+
|
|
35
|
+
## Token Obfuscation
|
|
36
|
+
|
|
37
|
+
Token obfuscation protects sensitive tokens (API keys, session tokens) in transit by:
|
|
38
|
+
|
|
39
|
+
1. Adding a timestamp for time-limited validity
|
|
40
|
+
2. Including a random nonce for uniqueness
|
|
41
|
+
3. Computing an HMAC signature bound to the request path
|
|
42
|
+
4. Encoding the result in a structured format
|
|
43
|
+
|
|
44
|
+
### Why Obfuscate?
|
|
45
|
+
|
|
46
|
+
- **Path Binding**: A token obfuscated for `/api/users` cannot be reused for `/api/admin`
|
|
47
|
+
- **Time Limiting**: Tokens expire after a short window (default: 5 minutes)
|
|
48
|
+
- **Replay Prevention**: Random nonce ensures each request is unique
|
|
49
|
+
- **Signature Verification**: HMAC ensures token integrity
|
|
50
|
+
|
|
51
|
+
## Token Format
|
|
52
|
+
|
|
53
|
+
Obfuscated tokens follow this structure:
|
|
54
|
+
|
|
55
|
+
```
|
|
56
|
+
v1:<timestamp>:<nonce>:<signature>:<token>
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
| Component | Description |
|
|
60
|
+
|-----------|-------------|
|
|
61
|
+
| `v1` | Version identifier for future format changes |
|
|
62
|
+
| `timestamp` | Unix milliseconds when token was obfuscated |
|
|
63
|
+
| `nonce` | Random 16-character string |
|
|
64
|
+
| `signature` | HMAC-SHA256 of `token:timestamp:nonce:path` |
|
|
65
|
+
| `token` | The original raw token |
|
|
66
|
+
|
|
67
|
+
### Example
|
|
68
|
+
|
|
69
|
+
```
|
|
70
|
+
v1:1704067200000:a1b2c3d4e5f6g7h8:8f4e2c1a9b7d5e3f1a2b4c6d8e0f2a4b6:arch_sk_live_xxx
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## API Reference
|
|
74
|
+
|
|
75
|
+
### `obfuscateToken(token, path, options?)`
|
|
76
|
+
|
|
77
|
+
Obfuscate a raw token for a specific API path.
|
|
78
|
+
|
|
79
|
+
```ts
|
|
80
|
+
import { obfuscateToken } from "@archlast/shared";
|
|
81
|
+
|
|
82
|
+
const obfuscated = obfuscateToken(
|
|
83
|
+
"arch_sk_live_abc123",
|
|
84
|
+
"/_archlast/admin/auth/me"
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
// Result: "v1:1704067200000:a1b2c3d4e5f6g7h8:..."
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
**Parameters:**
|
|
91
|
+
|
|
92
|
+
| Parameter | Type | Description |
|
|
93
|
+
|-----------|------|-------------|
|
|
94
|
+
| `token` | `string` | Raw token to obfuscate |
|
|
95
|
+
| `path` | `string` | API path the token is bound to |
|
|
96
|
+
| `options.secret` | `string?` | Custom HMAC secret (default: env var) |
|
|
97
|
+
| `options.timestamp` | `number?` | Custom timestamp (default: now) |
|
|
98
|
+
|
|
99
|
+
### `deobfuscateToken(obfuscated, path, options?)`
|
|
100
|
+
|
|
101
|
+
Verify and extract a token from its obfuscated form.
|
|
102
|
+
|
|
103
|
+
```ts
|
|
104
|
+
import { deobfuscateToken } from "@archlast/shared";
|
|
105
|
+
|
|
106
|
+
const result = deobfuscateToken(
|
|
107
|
+
"v1:1704067200000:...",
|
|
108
|
+
"/_archlast/admin/auth/me"
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
if (result.valid) {
|
|
112
|
+
console.log("Token:", result.token);
|
|
113
|
+
} else {
|
|
114
|
+
console.error("Invalid:", result.error);
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
**Returns:**
|
|
119
|
+
|
|
120
|
+
```ts
|
|
121
|
+
// Success
|
|
122
|
+
{ valid: true, token: string }
|
|
123
|
+
|
|
124
|
+
// Failure
|
|
125
|
+
{ valid: false, error: string }
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
**Possible Errors:**
|
|
129
|
+
|
|
130
|
+
| Error | Description |
|
|
131
|
+
|-------|-------------|
|
|
132
|
+
| `"Invalid format"` | Token doesn't match expected structure |
|
|
133
|
+
| `"Unsupported version"` | Version prefix is not recognized |
|
|
134
|
+
| `"Token expired"` | Timestamp is outside validity window |
|
|
135
|
+
| `"Invalid signature"` | HMAC verification failed |
|
|
136
|
+
| `"Path mismatch"` | Token was created for a different path |
|
|
137
|
+
|
|
138
|
+
### `extractRawToken(obfuscated, path, options?)`
|
|
139
|
+
|
|
140
|
+
Convenience wrapper that returns the raw token or throws.
|
|
141
|
+
|
|
142
|
+
```ts
|
|
143
|
+
import { extractRawToken } from "@archlast/shared";
|
|
144
|
+
|
|
145
|
+
try {
|
|
146
|
+
const rawToken = extractRawToken(obfuscated, path);
|
|
147
|
+
// Use rawToken...
|
|
148
|
+
} catch (error) {
|
|
149
|
+
console.error("Token validation failed:", error.message);
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### `isObfuscatedToken(value)`
|
|
154
|
+
|
|
155
|
+
Check if a string looks like an obfuscated token.
|
|
156
|
+
|
|
157
|
+
```ts
|
|
158
|
+
import { isObfuscatedToken } from "@archlast/shared";
|
|
159
|
+
|
|
160
|
+
isObfuscatedToken("v1:123:abc:def:token"); // true
|
|
161
|
+
isObfuscatedToken("arch_sk_live_xxx"); // false
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
## Configuration
|
|
165
|
+
|
|
166
|
+
### Environment Variables
|
|
167
|
+
|
|
168
|
+
| Variable | Default | Description |
|
|
169
|
+
|----------|---------|-------------|
|
|
170
|
+
| `ARCHLAST_TOKEN_OBFUSCATION_SECRET` | - | **Required in production.** HMAC secret for signatures. |
|
|
171
|
+
| `ARCHLAST_TOKEN_VALIDITY_MS` | `300000` | Token validity window (5 minutes) |
|
|
172
|
+
|
|
173
|
+
### Setting the Secret
|
|
174
|
+
|
|
175
|
+
```bash
|
|
176
|
+
# Generate a strong secret
|
|
177
|
+
openssl rand -base64 32
|
|
178
|
+
|
|
179
|
+
# Set in environment
|
|
180
|
+
export ARCHLAST_TOKEN_OBFUSCATION_SECRET="your-32-char-random-secret-here"
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### Custom Options
|
|
184
|
+
|
|
185
|
+
```ts
|
|
186
|
+
const obfuscated = obfuscateToken(token, path, {
|
|
187
|
+
secret: "custom-secret",
|
|
188
|
+
timestamp: Date.now(),
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
const result = deobfuscateToken(obfuscated, path, {
|
|
192
|
+
secret: "custom-secret",
|
|
193
|
+
validityMs: 60000, // 1 minute
|
|
194
|
+
});
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
## Security Considerations
|
|
198
|
+
|
|
199
|
+
### Production Requirements
|
|
200
|
+
|
|
201
|
+
1. **Set a Strong Secret**: Always set `ARCHLAST_TOKEN_OBFUSCATION_SECRET` in production
|
|
202
|
+
2. **Keep Clocks Synced**: Use NTP to keep server clocks synchronized
|
|
203
|
+
3. **Use HTTPS**: Always transmit tokens over HTTPS
|
|
204
|
+
4. **Short Validity**: Keep the validity window short (default: 5 minutes)
|
|
205
|
+
|
|
206
|
+
### Best Practices
|
|
207
|
+
|
|
208
|
+
```ts
|
|
209
|
+
// ✅ Good: Obfuscate before sending
|
|
210
|
+
const headers = {
|
|
211
|
+
Authorization: `Bearer ${obfuscateToken(apiKey, requestPath)}`
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
// ❌ Bad: Sending raw tokens in headers
|
|
215
|
+
const headers = {
|
|
216
|
+
Authorization: `Bearer ${apiKey}`
|
|
217
|
+
};
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
### Clock Skew
|
|
221
|
+
|
|
222
|
+
The validation allows for some clock skew between client and server:
|
|
223
|
+
|
|
224
|
+
```ts
|
|
225
|
+
// Token is valid if:
|
|
226
|
+
// (serverTime - validityMs) < tokenTimestamp < (serverTime + clockSkew)
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
Default clock skew allowance: 30 seconds.
|
|
230
|
+
|
|
231
|
+
## Internal Usage
|
|
232
|
+
|
|
233
|
+
This package is used internally by:
|
|
234
|
+
|
|
235
|
+
- **@archlast/server**: Validating incoming requests
|
|
236
|
+
- **@archlast/client**: Preparing authenticated requests
|
|
237
|
+
- **@archlast/cli**: API key handling for deployments
|
|
238
|
+
|
|
239
|
+
### Example: Server Middleware
|
|
240
|
+
|
|
241
|
+
```ts
|
|
242
|
+
// Internal usage in @archlast/server
|
|
243
|
+
import { deobfuscateToken } from "@archlast/shared";
|
|
244
|
+
|
|
245
|
+
function authMiddleware(req, res, next) {
|
|
246
|
+
const authHeader = req.headers.authorization;
|
|
247
|
+
const token = authHeader?.replace("Bearer ", "");
|
|
248
|
+
|
|
249
|
+
if (!token) {
|
|
250
|
+
return res.status(401).json({ error: "No token" });
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const result = deobfuscateToken(token, req.path);
|
|
254
|
+
|
|
255
|
+
if (!result.valid) {
|
|
256
|
+
return res.status(401).json({ error: result.error });
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
req.apiKey = result.token;
|
|
260
|
+
next();
|
|
261
|
+
}
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
## Publishing (Maintainers)
|
|
9
265
|
|
|
10
266
|
See `docs/npm-publishing.md` for release and publish steps.
|
|
267
|
+
|
|
268
|
+
## License
|
|
269
|
+
|
|
270
|
+
MIT
|