wirejs-resources 0.1.10 → 0.1.12
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 +3 -0
- package/dist/adapters/signed-cookie.d.ts +14 -0
- package/dist/adapters/signed-cookie.js +66 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.js +2 -0
- package/dist/internal/index.d.ts +1 -1
- package/dist/internal/index.js +4 -6
- package/dist/services/authentication.d.ts +6 -7
- package/dist/services/authentication.js +13 -59
- package/dist/types.d.ts +5 -2
- package/package.json +3 -2
package/README.md
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Secret } from '../resources/secret.js';
|
|
2
|
+
import { Cookie, CookieJar } from './cookie-jar.js';
|
|
3
|
+
import { Resource } from '../resource.js';
|
|
4
|
+
export declare class SignedCookie<T> {
|
|
5
|
+
private scope;
|
|
6
|
+
private name;
|
|
7
|
+
private signingSecret;
|
|
8
|
+
private options;
|
|
9
|
+
constructor(scope: Resource | string, name: string, signingSecret: Secret, options?: Omit<Cookie, 'value' | 'name'>);
|
|
10
|
+
get cookieName(): string;
|
|
11
|
+
read(cookies: CookieJar): Promise<T | null>;
|
|
12
|
+
write(cookies: CookieJar, value: T | null): Promise<void>;
|
|
13
|
+
clear(cookies: CookieJar): void;
|
|
14
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import * as jose from 'jose';
|
|
2
|
+
const ONE_HOUR = 60 * 60; // minutes/hour * seconds/minute
|
|
3
|
+
export class SignedCookie {
|
|
4
|
+
scope;
|
|
5
|
+
name;
|
|
6
|
+
signingSecret;
|
|
7
|
+
options;
|
|
8
|
+
constructor(scope, name, signingSecret, options = {}) {
|
|
9
|
+
this.scope = scope;
|
|
10
|
+
this.name = name;
|
|
11
|
+
this.signingSecret = signingSecret;
|
|
12
|
+
this.options = options;
|
|
13
|
+
}
|
|
14
|
+
get cookieName() {
|
|
15
|
+
if (typeof this.scope === 'string') {
|
|
16
|
+
return `${this.scope.replaceAll('/', '-')}-${this.name}`;
|
|
17
|
+
}
|
|
18
|
+
else {
|
|
19
|
+
return `${this.scope.absoluteId.replaceAll('/', '-')}-${this.name}`;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
async read(cookies) {
|
|
23
|
+
try {
|
|
24
|
+
const cookie = cookies.get(this.cookieName)?.value;
|
|
25
|
+
const result = cookie ? await jose.jwtVerify(cookie, new TextEncoder().encode(await this.signingSecret.read())) : undefined;
|
|
26
|
+
if (result?.payload) {
|
|
27
|
+
return result.payload;
|
|
28
|
+
}
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
async write(cookies, value) {
|
|
36
|
+
if (!value) {
|
|
37
|
+
this.clear(cookies);
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
const duration = typeof this.options.maxAge === 'number'
|
|
41
|
+
? this.options.maxAge
|
|
42
|
+
: ONE_HOUR;
|
|
43
|
+
const httpOnly = typeof this.options.httpOnly === 'boolean'
|
|
44
|
+
? this.options.httpOnly
|
|
45
|
+
: true;
|
|
46
|
+
const secure = typeof this.options.secure === 'boolean'
|
|
47
|
+
? this.options.secure
|
|
48
|
+
: true;
|
|
49
|
+
const jwt = await new jose.SignJWT(value)
|
|
50
|
+
.setProtectedHeader({ alg: 'HS256' })
|
|
51
|
+
.setIssuedAt()
|
|
52
|
+
.setExpirationTime(`${duration}s`)
|
|
53
|
+
.sign(new TextEncoder().encode(await this.signingSecret.read()));
|
|
54
|
+
cookies.set({
|
|
55
|
+
name: this.cookieName,
|
|
56
|
+
value: jwt,
|
|
57
|
+
httpOnly,
|
|
58
|
+
secure,
|
|
59
|
+
maxAge: duration
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
clear(cookies) {
|
|
64
|
+
cookies.delete(this.cookieName);
|
|
65
|
+
}
|
|
66
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
export { FileService } from './services/file.js';
|
|
2
2
|
export { AuthenticationService } from './services/authentication.js';
|
|
3
3
|
export { CookieJar } from './adapters/cookie-jar.js';
|
|
4
|
-
export {
|
|
4
|
+
export { SignedCookie } from './adapters/signed-cookie.js';
|
|
5
|
+
export { withContext, requiresContext, Context, ContextWrapped } from './adapters/context.js';
|
|
5
6
|
export { Resource } from './resource.js';
|
|
6
7
|
export { overrides } from './overrides.js';
|
|
8
|
+
export { Secret } from './resources/secret.js';
|
|
7
9
|
export * from './types.js';
|
|
8
10
|
export * from './derived-types.js';
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
export { FileService } from './services/file.js';
|
|
2
2
|
export { AuthenticationService } from './services/authentication.js';
|
|
3
3
|
export { CookieJar } from './adapters/cookie-jar.js';
|
|
4
|
+
export { SignedCookie } from './adapters/signed-cookie.js';
|
|
4
5
|
export { withContext, requiresContext, Context } from './adapters/context.js';
|
|
5
6
|
export { Resource } from './resource.js';
|
|
6
7
|
export { overrides } from './overrides.js';
|
|
8
|
+
export { Secret } from './resources/secret.js';
|
|
7
9
|
export * from './types.js';
|
|
8
10
|
export * from './derived-types.js';
|
package/dist/internal/index.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare function prebuildApi(): Promise<void>;
|
|
1
|
+
export declare function prebuildApi(apiDir: string): Promise<void>;
|
package/dist/internal/index.js
CHANGED
|
@@ -1,12 +1,10 @@
|
|
|
1
|
-
import process from 'process';
|
|
2
1
|
import fs from 'fs';
|
|
3
2
|
import path from 'path';
|
|
4
|
-
export async function prebuildApi() {
|
|
5
|
-
const CWD = process.cwd();
|
|
3
|
+
export async function prebuildApi(apiDir) {
|
|
6
4
|
let API_URL = '/api';
|
|
7
|
-
const indexModule = await import(path.join(
|
|
5
|
+
const indexModule = await import(path.join(apiDir, 'dist', 'index.js'));
|
|
8
6
|
try {
|
|
9
|
-
const backendConfigModule = await import(path.join(
|
|
7
|
+
const backendConfigModule = await import(path.join(apiDir, 'config.js'));
|
|
10
8
|
const backendConfig = backendConfigModule.default;
|
|
11
9
|
console.log("backend config found", backendConfig);
|
|
12
10
|
if (backendConfig.apiUrl) {
|
|
@@ -23,5 +21,5 @@ export async function prebuildApi() {
|
|
|
23
21
|
`import { apiTree } from "wirejs-resources/client";`,
|
|
24
22
|
`const INTERNAL_API_URL = ${JSON.stringify(API_URL)};`,
|
|
25
23
|
].join('\n');
|
|
26
|
-
await fs.promises.writeFile(path.join(
|
|
24
|
+
await fs.promises.writeFile(path.join(apiDir, 'dist', 'index.client.js'), [baseClient, apiCode].join('\n\n'));
|
|
27
25
|
}
|
|
@@ -1,21 +1,20 @@
|
|
|
1
1
|
import { Resource } from '../resource.js';
|
|
2
|
+
import { ContextWrapped } from '../adapters/context.js';
|
|
2
3
|
import type { CookieJar } from '../adapters/cookie-jar.js';
|
|
3
|
-
import type { User, AuthenticationError, AuthenticationMachineInput, AuthenticationMachineState, AuthenticationServiceOptions
|
|
4
|
+
import type { User, AuthenticationError, AuthenticationMachineInput, AuthenticationMachineState, AuthenticationServiceOptions } from '../types.js';
|
|
4
5
|
export declare class AuthenticationService extends Resource {
|
|
5
6
|
#private;
|
|
6
7
|
constructor(scope: Resource | string, id: string, { duration, keepalive, cookie }?: AuthenticationServiceOptions);
|
|
7
|
-
|
|
8
|
-
get signingSecret(): Promise<Uint8Array<ArrayBufferLike>>;
|
|
9
|
-
getState(cookies: CookieJar): Promise<AuthenticationState>;
|
|
8
|
+
private getState;
|
|
10
9
|
getMachineState(cookies: CookieJar): Promise<AuthenticationMachineState>;
|
|
11
|
-
setState
|
|
10
|
+
private setState;
|
|
12
11
|
missingFieldErrors(input: Record<string, string | number | boolean>, fields: string[]): AuthenticationError[] | undefined;
|
|
13
12
|
setMachineState(cookies: CookieJar, form: AuthenticationMachineInput): Promise<AuthenticationMachineState | {
|
|
14
13
|
errors: AuthenticationError[];
|
|
15
14
|
}>;
|
|
16
|
-
buildApi(
|
|
15
|
+
buildApi(): ContextWrapped<{
|
|
17
16
|
getState: () => Promise<AuthenticationMachineState>;
|
|
18
|
-
setState: (options:
|
|
17
|
+
setState: (options: AuthenticationMachineInput) => Promise<AuthenticationMachineState | {
|
|
19
18
|
errors: AuthenticationError[];
|
|
20
19
|
}>;
|
|
21
20
|
getCurrentUser: () => Promise<User | null>;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { scrypt, randomBytes, randomUUID } from 'crypto';
|
|
2
|
-
import * as jose from 'jose';
|
|
3
2
|
import { Resource } from '../resource.js';
|
|
4
3
|
import { FileService } from './file.js';
|
|
5
4
|
import { Secret } from '../resources/secret.js';
|
|
5
|
+
import { SignedCookie } from '../adapters/signed-cookie.js';
|
|
6
6
|
import { withContext } from '../adapters/context.js';
|
|
7
7
|
import { overrides } from '../overrides.js';
|
|
8
8
|
function newId() {
|
|
@@ -181,60 +181,23 @@ class UserStore {
|
|
|
181
181
|
}
|
|
182
182
|
}
|
|
183
183
|
export class AuthenticationService extends Resource {
|
|
184
|
-
#duration;
|
|
185
|
-
;
|
|
186
184
|
#keepalive;
|
|
187
|
-
#cookieName;
|
|
188
|
-
#rawSigningSecret;
|
|
189
|
-
#signingSecret;
|
|
190
185
|
#users;
|
|
186
|
+
#cookie;
|
|
191
187
|
constructor(scope, id, { duration, keepalive, cookie } = {}) {
|
|
192
188
|
super(scope, id);
|
|
193
|
-
this.#duration = duration || ONE_WEEK;
|
|
194
189
|
this.#keepalive = !!keepalive;
|
|
195
|
-
|
|
196
|
-
this.#rawSigningSecret = new (overrides.Secret || Secret)(this, 'jwt-signing-secret');
|
|
190
|
+
const signingSecret = new (overrides.Secret || Secret)(this, 'jwt-signing-secret');
|
|
197
191
|
const fileService = new (overrides.FileService || FileService)(this, 'files');
|
|
192
|
+
this.#cookie = new SignedCookie(this, cookie ?? 'identity', signingSecret, { maxAge: ONE_WEEK });
|
|
198
193
|
this.#users = new UserStore(fileService);
|
|
199
194
|
}
|
|
200
|
-
async getSigningSecret() {
|
|
201
|
-
const secretAsString = await this.#rawSigningSecret.read();
|
|
202
|
-
return new TextEncoder().encode(secretAsString);
|
|
203
|
-
}
|
|
204
|
-
get signingSecret() {
|
|
205
|
-
if (!this.#signingSecret) {
|
|
206
|
-
this.#signingSecret = this.getSigningSecret();
|
|
207
|
-
}
|
|
208
|
-
return this.#signingSecret;
|
|
209
|
-
}
|
|
210
195
|
async getState(cookies) {
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
user = idPayload ? {
|
|
217
|
-
id: idPayload.payload.sub,
|
|
218
|
-
username: idPayload.payload.username,
|
|
219
|
-
displayName: idPayload.payload.username,
|
|
220
|
-
} : undefined;
|
|
221
|
-
}
|
|
222
|
-
catch (err) {
|
|
223
|
-
// jose doesn't like our cookie.
|
|
224
|
-
console.error(err);
|
|
225
|
-
}
|
|
226
|
-
if (user) {
|
|
227
|
-
return {
|
|
228
|
-
state: 'authenticated',
|
|
229
|
-
user
|
|
230
|
-
};
|
|
231
|
-
}
|
|
232
|
-
else {
|
|
233
|
-
return {
|
|
234
|
-
state: 'unauthenticated',
|
|
235
|
-
user: undefined,
|
|
236
|
-
};
|
|
237
|
-
}
|
|
196
|
+
const cookieState = await this.#cookie.read(cookies);
|
|
197
|
+
return cookieState ? cookieState : {
|
|
198
|
+
state: 'unauthenticated',
|
|
199
|
+
user: undefined
|
|
200
|
+
};
|
|
238
201
|
}
|
|
239
202
|
async getMachineState(cookies) {
|
|
240
203
|
const state = await this.getState(cookies);
|
|
@@ -261,21 +224,12 @@ export class AuthenticationService extends Resource {
|
|
|
261
224
|
}
|
|
262
225
|
async setState(cookies, user) {
|
|
263
226
|
if (!user) {
|
|
264
|
-
|
|
227
|
+
this.#cookie.clear(cookies);
|
|
265
228
|
}
|
|
266
229
|
else {
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
.setSubject(user.id)
|
|
271
|
-
.setExpirationTime(`${this.#duration}s`)
|
|
272
|
-
.sign(await this.signingSecret);
|
|
273
|
-
cookies.set({
|
|
274
|
-
name: this.#cookieName,
|
|
275
|
-
value: jwt,
|
|
276
|
-
httpOnly: true,
|
|
277
|
-
secure: true,
|
|
278
|
-
maxAge: this.#duration
|
|
230
|
+
return this.#cookie.write(cookies, {
|
|
231
|
+
state: 'authenticated',
|
|
232
|
+
user
|
|
279
233
|
});
|
|
280
234
|
}
|
|
281
235
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -27,8 +27,11 @@ export type AuthenticationAction = {
|
|
|
27
27
|
buttons?: readonly string[];
|
|
28
28
|
};
|
|
29
29
|
export type AuthenticationState = {
|
|
30
|
-
state: 'authenticated'
|
|
31
|
-
user: User
|
|
30
|
+
state: 'authenticated';
|
|
31
|
+
user: User;
|
|
32
|
+
} | {
|
|
33
|
+
state: 'unauthenticated';
|
|
34
|
+
user: undefined;
|
|
32
35
|
};
|
|
33
36
|
export type AuthenticationMachineAction = Readonly<AuthenticationAction & {
|
|
34
37
|
key: string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wirejs-resources",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.12",
|
|
4
4
|
"description": "Basic services and server-side resources for wirejs apps",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -40,6 +40,7 @@
|
|
|
40
40
|
},
|
|
41
41
|
"files": [
|
|
42
42
|
"package.json",
|
|
43
|
+
"README.md",
|
|
43
44
|
"dist/*"
|
|
44
45
|
]
|
|
45
|
-
}
|
|
46
|
+
}
|