@starci/auth 0.0.0-stage → 0.2.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/CHANGELOG.md +16 -0
- package/LICENSE +21 -0
- package/README.md +38 -2
- package/dist/client/auth-client.d.ts +68 -0
- package/dist/client/auth-client.js +192 -0
- package/dist/client/index.d.ts +8 -0
- package/dist/client/index.js +4 -0
- package/dist/client/outcome.d.ts +15 -0
- package/dist/client/outcome.js +22 -0
- package/dist/client/session.d.ts +57 -0
- package/dist/client/session.js +68 -0
- package/dist/client/shared.d.ts +20 -0
- package/dist/client/shared.js +42 -0
- package/dist/contracts/index.d.ts +111 -0
- package/dist/contracts/index.js +71 -0
- package/dist/react/index.d.ts +21 -0
- package/dist/react/index.js +42 -0
- package/dist/server/auth.module.d.ts +29 -0
- package/dist/server/auth.module.js +85 -0
- package/dist/server/auth.tokens.d.ts +24 -0
- package/dist/server/auth.tokens.js +24 -0
- package/dist/server/budget.d.ts +21 -0
- package/dist/server/budget.js +38 -0
- package/dist/server/cookie.d.ts +19 -0
- package/dist/server/cookie.js +54 -0
- package/dist/server/doors.d.ts +44 -0
- package/dist/server/doors.js +72 -0
- package/dist/server/errors.d.ts +24 -0
- package/dist/server/errors.js +18 -0
- package/dist/server/flows.d.ts +21 -0
- package/dist/server/flows.js +66 -0
- package/dist/server/google-state.d.ts +33 -0
- package/dist/server/google-state.js +73 -0
- package/dist/server/http.d.ts +27 -0
- package/dist/server/http.js +1 -0
- package/dist/server/index.d.ts +24 -0
- package/dist/server/index.js +15 -0
- package/dist/server/options.d.ts +81 -0
- package/dist/server/options.js +1 -0
- package/dist/server/passwords.d.ts +11 -0
- package/dist/server/passwords.js +22 -0
- package/dist/server/ports.d.ts +145 -0
- package/dist/server/ports.js +1 -0
- package/dist/server/primitives.d.ts +36 -0
- package/dist/server/primitives.js +67 -0
- package/dist/server/runtime.d.ts +5 -0
- package/dist/server/runtime.js +5 -0
- package/dist/server/sessions.d.ts +45 -0
- package/dist/server/sessions.js +153 -0
- package/dist/server/strategies.d.ts +29 -0
- package/dist/server/strategies.js +55 -0
- package/dist/server/tokens.d.ts +26 -0
- package/dist/server/tokens.js +130 -0
- package/dist/server/verifier.d.ts +20 -0
- package/dist/server/verifier.js +23 -0
- package/dist/testing/index.d.ts +52 -0
- package/dist/testing/index.js +158 -0
- package/migrations/identity-sessions.sql +58 -0
- package/package.json +126 -4
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { AuthOptions, SigningKeyOptions } from "../server/options.js";
|
|
2
|
+
import type { AccountDirectory, AccountStanding, AuthClock, AuthIds, NewSession, RateCounter, RefreshTokenRecord, SessionStore, SessionTransaction } from "../server/ports.js";
|
|
3
|
+
/** A generated Ed25519 key pair as the PEM texts the options take. */
|
|
4
|
+
export interface TestKeyPair {
|
|
5
|
+
readonly privateKey: string;
|
|
6
|
+
readonly publicKey: string;
|
|
7
|
+
}
|
|
8
|
+
/** Generates an Ed25519 key pair (PKCS#8 private PEM, SPKI public PEM). */
|
|
9
|
+
export declare const generateTestKeyPair: () => TestKeyPair;
|
|
10
|
+
/** A signing key for the options; `signer: false` makes it a verify-only (retired) key. */
|
|
11
|
+
export declare const testSigningKey: (pair?: TestKeyPair, keyId?: string, signer?: boolean) => SigningKeyOptions;
|
|
12
|
+
/** A clock the test moves by hand. */
|
|
13
|
+
export declare class FakeClock implements AuthClock {
|
|
14
|
+
#private;
|
|
15
|
+
constructor(start?: Date | number);
|
|
16
|
+
now(): Date;
|
|
17
|
+
/** Moves time forward. */
|
|
18
|
+
advance(milliseconds: number): void;
|
|
19
|
+
}
|
|
20
|
+
/** Ids that count up, so a spec reads them. */
|
|
21
|
+
export declare const sequentialIds: (prefix?: string) => AuthIds;
|
|
22
|
+
/** An in-memory `SessionStore` with the same rotation semantics the SQL tables give (a transaction applies all or none of its writes). */
|
|
23
|
+
export declare class MemorySessionStore implements SessionStore {
|
|
24
|
+
#private;
|
|
25
|
+
readonly sessions: Map<string, NewSession>;
|
|
26
|
+
readonly tokens: Map<string, RefreshTokenRecord>;
|
|
27
|
+
transaction<T>(work: (tx: SessionTransaction) => Promise<T>): Promise<T>;
|
|
28
|
+
findToken(tokenHash: string): Promise<RefreshTokenRecord | null>;
|
|
29
|
+
touchSession(): Promise<void>;
|
|
30
|
+
closeSession(sessionId: string): Promise<void>;
|
|
31
|
+
closeOtherSessions(userId: string, keepSessionId: string): Promise<void>;
|
|
32
|
+
}
|
|
33
|
+
/** An in-memory account directory. */
|
|
34
|
+
export declare class MemoryAccounts implements AccountDirectory {
|
|
35
|
+
readonly accounts: Map<string, AccountStanding>;
|
|
36
|
+
/** Adds or replaces an account. */
|
|
37
|
+
set(account: AccountStanding): this;
|
|
38
|
+
find(userId: string): Promise<AccountStanding | undefined>;
|
|
39
|
+
}
|
|
40
|
+
/** An in-memory rate counter with fixed windows that the clock moves. */
|
|
41
|
+
export declare class MemoryRateCounter implements RateCounter {
|
|
42
|
+
#private;
|
|
43
|
+
constructor(clock: AuthClock);
|
|
44
|
+
consume(key: string, windowMs: number, limit: number): Promise<boolean>;
|
|
45
|
+
clear(key: string): Promise<void>;
|
|
46
|
+
}
|
|
47
|
+
/** Options for a spec: one fresh signing key, in-memory stores and a fake clock. Override any field. */
|
|
48
|
+
export declare const testAuthOptions: (overrides?: Partial<Omit<AuthOptions, "clock" | "sessions" | "accounts">>) => AuthOptions & {
|
|
49
|
+
readonly clock: FakeClock;
|
|
50
|
+
readonly sessions: MemorySessionStore;
|
|
51
|
+
readonly accounts: MemoryAccounts;
|
|
52
|
+
};
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { generateKeyPairSync, randomUUID } from "node:crypto";
|
|
2
|
+
/** Generates an Ed25519 key pair (PKCS#8 private PEM, SPKI public PEM). */
|
|
3
|
+
export const generateTestKeyPair = () => {
|
|
4
|
+
const { privateKey, publicKey } = generateKeyPairSync("ed25519");
|
|
5
|
+
return {
|
|
6
|
+
privateKey: privateKey.export({ type: "pkcs8", format: "pem" }).toString(),
|
|
7
|
+
publicKey: publicKey.export({ type: "spki", format: "pem" }).toString(),
|
|
8
|
+
};
|
|
9
|
+
};
|
|
10
|
+
/** A signing key for the options; `signer: false` makes it a verify-only (retired) key. */
|
|
11
|
+
export const testSigningKey = (pair = generateTestKeyPair(), keyId, signer = true) => ({
|
|
12
|
+
publicKey: pair.publicKey,
|
|
13
|
+
...(signer ? { privateKey: pair.privateKey } : {}),
|
|
14
|
+
...(keyId === undefined ? {} : { keyId }),
|
|
15
|
+
});
|
|
16
|
+
/** A clock the test moves by hand. */
|
|
17
|
+
export class FakeClock {
|
|
18
|
+
#at;
|
|
19
|
+
constructor(start = Date.UTC(2026, 0, 1)) {
|
|
20
|
+
this.#at = typeof start === "number" ? start : start.getTime();
|
|
21
|
+
}
|
|
22
|
+
now() {
|
|
23
|
+
return new Date(this.#at);
|
|
24
|
+
}
|
|
25
|
+
/** Moves time forward. */
|
|
26
|
+
advance(milliseconds) {
|
|
27
|
+
this.#at += milliseconds;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/** Ids that count up, so a spec reads them. */
|
|
31
|
+
export const sequentialIds = (prefix = "id") => {
|
|
32
|
+
let n = 0;
|
|
33
|
+
return { next: () => `${prefix}-${String(++n)}-${randomUUID().slice(0, 8)}` };
|
|
34
|
+
};
|
|
35
|
+
/** An in-memory `SessionStore` with the same rotation semantics the SQL tables give (a transaction applies all or none of its writes). */
|
|
36
|
+
export class MemorySessionStore {
|
|
37
|
+
sessions = new Map();
|
|
38
|
+
tokens = new Map();
|
|
39
|
+
async transaction(work) {
|
|
40
|
+
const sessions = new Map(this.sessions);
|
|
41
|
+
const tokens = new Map(this.tokens);
|
|
42
|
+
const tx = {
|
|
43
|
+
openSession: (session) => {
|
|
44
|
+
this.sessions.set(session.id, session);
|
|
45
|
+
return Promise.resolve();
|
|
46
|
+
},
|
|
47
|
+
insertToken: (token) => {
|
|
48
|
+
this.tokens.set(token.id, { ...token, usedAt: null });
|
|
49
|
+
return Promise.resolve();
|
|
50
|
+
},
|
|
51
|
+
findTokenForUpdate: (tokenHash) => Promise.resolve(this.#byHash(tokenHash)),
|
|
52
|
+
markTokenUsed: (tokenId, at) => {
|
|
53
|
+
const row = this.tokens.get(tokenId);
|
|
54
|
+
if (row !== undefined)
|
|
55
|
+
this.tokens.set(tokenId, { ...row, usedAt: at });
|
|
56
|
+
return Promise.resolve();
|
|
57
|
+
},
|
|
58
|
+
deleteExpiredTokens: (userId, before) => {
|
|
59
|
+
for (const [id, row] of this.tokens)
|
|
60
|
+
if (row.userId === userId && row.expiresAt < before)
|
|
61
|
+
this.tokens.delete(id);
|
|
62
|
+
return Promise.resolve();
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
try {
|
|
66
|
+
return await work(tx);
|
|
67
|
+
}
|
|
68
|
+
catch (cause) {
|
|
69
|
+
this.sessions.clear();
|
|
70
|
+
this.tokens.clear();
|
|
71
|
+
for (const [id, row] of sessions)
|
|
72
|
+
this.sessions.set(id, row);
|
|
73
|
+
for (const [id, row] of tokens)
|
|
74
|
+
this.tokens.set(id, row);
|
|
75
|
+
throw cause;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
findToken(tokenHash) {
|
|
79
|
+
return Promise.resolve(this.#byHash(tokenHash));
|
|
80
|
+
}
|
|
81
|
+
touchSession() {
|
|
82
|
+
return Promise.resolve();
|
|
83
|
+
}
|
|
84
|
+
closeSession(sessionId) {
|
|
85
|
+
this.sessions.delete(sessionId);
|
|
86
|
+
for (const [id, row] of this.tokens)
|
|
87
|
+
if (row.sessionId === sessionId)
|
|
88
|
+
this.tokens.delete(id);
|
|
89
|
+
return Promise.resolve();
|
|
90
|
+
}
|
|
91
|
+
closeOtherSessions(userId, keepSessionId) {
|
|
92
|
+
for (const [id, row] of this.sessions)
|
|
93
|
+
if (row.userId === userId && id !== keepSessionId)
|
|
94
|
+
void this.closeSession(id);
|
|
95
|
+
return Promise.resolve();
|
|
96
|
+
}
|
|
97
|
+
#byHash(tokenHash) {
|
|
98
|
+
for (const row of this.tokens.values())
|
|
99
|
+
if (row.tokenHash === tokenHash)
|
|
100
|
+
return row;
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
/** An in-memory account directory. */
|
|
105
|
+
export class MemoryAccounts {
|
|
106
|
+
accounts = new Map();
|
|
107
|
+
/** Adds or replaces an account. */
|
|
108
|
+
set(account) {
|
|
109
|
+
this.accounts.set(account.id, account);
|
|
110
|
+
return this;
|
|
111
|
+
}
|
|
112
|
+
find(userId) {
|
|
113
|
+
return Promise.resolve(this.accounts.get(userId));
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
/** An in-memory rate counter with fixed windows that the clock moves. */
|
|
117
|
+
export class MemoryRateCounter {
|
|
118
|
+
#clock;
|
|
119
|
+
#hits = new Map();
|
|
120
|
+
constructor(clock) {
|
|
121
|
+
this.#clock = clock;
|
|
122
|
+
}
|
|
123
|
+
consume(key, windowMs, limit) {
|
|
124
|
+
const at = this.#clock.now().getTime();
|
|
125
|
+
const current = this.#hits.get(key);
|
|
126
|
+
const entry = current === undefined || current.resetAt <= at ? { count: 0, resetAt: at + windowMs } : current;
|
|
127
|
+
entry.count += 1;
|
|
128
|
+
this.#hits.set(key, entry);
|
|
129
|
+
return Promise.resolve(entry.count <= limit);
|
|
130
|
+
}
|
|
131
|
+
clear(key) {
|
|
132
|
+
this.#hits.delete(key);
|
|
133
|
+
return Promise.resolve();
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
/** Options for a spec: one fresh signing key, in-memory stores and a fake clock. Override any field. */
|
|
137
|
+
export const testAuthOptions = (overrides = {}) => {
|
|
138
|
+
const clock = new FakeClock();
|
|
139
|
+
const sessions = new MemorySessionStore();
|
|
140
|
+
const accounts = new MemoryAccounts();
|
|
141
|
+
return {
|
|
142
|
+
issuer: "https://api.example.test",
|
|
143
|
+
keys: [testSigningKey()],
|
|
144
|
+
accessTtlSeconds: 900,
|
|
145
|
+
refreshTtlSeconds: 2_592_000,
|
|
146
|
+
bcryptCost: 4,
|
|
147
|
+
returnOrigin: "https://app.example.test",
|
|
148
|
+
google: null,
|
|
149
|
+
cookie: { name: "auth_refresh" },
|
|
150
|
+
budget: { loginAttempts: 3, resetAttempts: 2, windowMs: 900_000 },
|
|
151
|
+
rate: new MemoryRateCounter(clock),
|
|
152
|
+
ids: sequentialIds(),
|
|
153
|
+
...overrides,
|
|
154
|
+
clock,
|
|
155
|
+
sessions,
|
|
156
|
+
accounts,
|
|
157
|
+
};
|
|
158
|
+
};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
-- The device sessions and rotating refresh tokens of @starci/auth. A token is stored by its sha256 only; a rotated token stays,
|
|
2
|
+
-- marked used, so a replay is detected (a replay inside the grace window is a racing tab, an older one ends the session).
|
|
3
|
+
-- identity_attempts counts the sign-in attempts of one address in a window every replica shares (the budget of failures).
|
|
4
|
+
-- The app owns auth.users (db/bootstrap.sql) and the role app_be.
|
|
5
|
+
|
|
6
|
+
create table public.identity_sessions (
|
|
7
|
+
id uuid primary key,
|
|
8
|
+
user_id uuid not null references auth.users (id) on delete cascade,
|
|
9
|
+
created_at timestamptz not null default now(),
|
|
10
|
+
refreshed_at timestamptz not null default now(),
|
|
11
|
+
user_agent text,
|
|
12
|
+
ip text
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
create index identity_sessions_user_idx on public.identity_sessions (user_id);
|
|
16
|
+
|
|
17
|
+
create table public.identity_refresh_tokens (
|
|
18
|
+
id uuid primary key default gen_random_uuid(),
|
|
19
|
+
session_id uuid not null references public.identity_sessions (id) on delete cascade,
|
|
20
|
+
user_id uuid not null,
|
|
21
|
+
token_hash text not null,
|
|
22
|
+
created_at timestamptz not null default now(),
|
|
23
|
+
expires_at timestamptz not null,
|
|
24
|
+
used_at timestamptz,
|
|
25
|
+
constraint identity_refresh_tokens_hash_key unique (token_hash)
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
create index identity_refresh_tokens_session_idx on public.identity_refresh_tokens (session_id);
|
|
29
|
+
create index identity_refresh_tokens_expiry_idx on public.identity_refresh_tokens (user_id, expires_at);
|
|
30
|
+
|
|
31
|
+
create table public.identity_attempts (
|
|
32
|
+
key varchar(128) primary key,
|
|
33
|
+
hits integer not null,
|
|
34
|
+
reset_at timestamptz not null
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
create index identity_attempts_reset_idx on public.identity_attempts (reset_at);
|
|
38
|
+
|
|
39
|
+
alter table public.identity_sessions enable row level security;
|
|
40
|
+
alter table public.identity_sessions force row level security;
|
|
41
|
+
alter table public.identity_refresh_tokens enable row level security;
|
|
42
|
+
alter table public.identity_refresh_tokens force row level security;
|
|
43
|
+
alter table public.identity_attempts enable row level security;
|
|
44
|
+
alter table public.identity_attempts force row level security;
|
|
45
|
+
|
|
46
|
+
revoke all on table public.identity_sessions from public;
|
|
47
|
+
revoke all on table public.identity_refresh_tokens from public;
|
|
48
|
+
revoke all on table public.identity_attempts from public;
|
|
49
|
+
grant select, insert, update, delete on table public.identity_sessions to app_be;
|
|
50
|
+
grant select, insert, update, delete on table public.identity_refresh_tokens to app_be;
|
|
51
|
+
grant select, insert, update, delete on table public.identity_attempts to app_be;
|
|
52
|
+
|
|
53
|
+
create policy identity_sessions_app_be_all on public.identity_sessions
|
|
54
|
+
for all to app_be using (current_user = 'app_be') with check (current_user = 'app_be');
|
|
55
|
+
create policy identity_refresh_tokens_app_be_all on public.identity_refresh_tokens
|
|
56
|
+
for all to app_be using (current_user = 'app_be') with check (current_user = 'app_be');
|
|
57
|
+
create policy identity_attempts_app_be_all on public.identity_attempts
|
|
58
|
+
for all to app_be using (current_user = 'app_be') with check (current_user = 'app_be');
|
package/package.json
CHANGED
|
@@ -1,6 +1,128 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@starci/auth",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "The sign-in of a StarCi lite app in one package: a NestJS module (EdDSA access tokens with a multi-key JWKS, bcrypt passwords, Google OAuth with state and PKCE, rotating refresh tokens with a reuse grace window and reuse detection, a failures-only sign-in limit, the refresh cookie), the door contracts, a front-end auth client (access token in memory, one shared refresh, signed out only on 401 or 403) with React bindings, and test doubles.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"auth",
|
|
7
|
+
"jwt",
|
|
8
|
+
"jwks",
|
|
9
|
+
"eddsa",
|
|
10
|
+
"passport",
|
|
11
|
+
"google-oauth",
|
|
12
|
+
"pkce",
|
|
13
|
+
"refresh-token",
|
|
14
|
+
"nestjs",
|
|
15
|
+
"react"
|
|
16
|
+
],
|
|
17
|
+
"license": "MIT",
|
|
18
|
+
"author": "starci183",
|
|
19
|
+
"homepage": "https://github.com/starci183/starci-harness/tree/main/packages/auth#readme",
|
|
20
|
+
"repository": {
|
|
21
|
+
"type": "git",
|
|
22
|
+
"url": "git+https://github.com/starci183/starci-harness.git",
|
|
23
|
+
"directory": "packages/auth"
|
|
24
|
+
},
|
|
25
|
+
"bugs": {
|
|
26
|
+
"url": "https://github.com/starci183/starci-harness/issues"
|
|
27
|
+
},
|
|
28
|
+
"private": false,
|
|
29
|
+
"type": "module",
|
|
30
|
+
"main": "./dist/contracts/index.js",
|
|
31
|
+
"types": "./dist/contracts/index.d.ts",
|
|
32
|
+
"exports": {
|
|
33
|
+
"./server": {
|
|
34
|
+
"types": "./dist/server/index.d.ts",
|
|
35
|
+
"default": "./dist/server/index.js"
|
|
36
|
+
},
|
|
37
|
+
"./client": {
|
|
38
|
+
"types": "./dist/client/index.d.ts",
|
|
39
|
+
"default": "./dist/client/index.js"
|
|
40
|
+
},
|
|
41
|
+
"./client/react": {
|
|
42
|
+
"types": "./dist/react/index.d.ts",
|
|
43
|
+
"default": "./dist/react/index.js"
|
|
44
|
+
},
|
|
45
|
+
"./contracts": {
|
|
46
|
+
"types": "./dist/contracts/index.d.ts",
|
|
47
|
+
"default": "./dist/contracts/index.js"
|
|
48
|
+
},
|
|
49
|
+
"./testing": {
|
|
50
|
+
"types": "./dist/testing/index.d.ts",
|
|
51
|
+
"default": "./dist/testing/index.js"
|
|
52
|
+
},
|
|
53
|
+
"./migrations/*": "./migrations/*",
|
|
54
|
+
"./package.json": "./package.json"
|
|
55
|
+
},
|
|
56
|
+
"files": [
|
|
57
|
+
"dist",
|
|
58
|
+
"!dist/**/*.spec.*",
|
|
59
|
+
"migrations",
|
|
60
|
+
"LICENSE",
|
|
61
|
+
"README.md",
|
|
62
|
+
"CHANGELOG.md"
|
|
63
|
+
],
|
|
64
|
+
"sideEffects": false,
|
|
65
|
+
"engines": {
|
|
66
|
+
"node": ">=20"
|
|
67
|
+
},
|
|
68
|
+
"scripts": {
|
|
69
|
+
"build": "node -e \"require('node:fs').rmSync('dist', {recursive: true, force: true})\" && tsc -p tsconfig.build.json && node -e \"const fs = require('node:fs'); for (const f of fs.readdirSync('dist', {recursive: true}).filter((n) => n.endsWith('.d.ts'))) fs.writeFileSync('dist/' + f, fs.readFileSync('dist/' + f, 'utf8').replace(/((?:from |import\\()['\\x22]\\.\\.?\\/[^'\\x22]+)\\.ts(['\\x22])/g, (all, head, quote) => head + '.js' + quote))\"",
|
|
70
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
71
|
+
"lint": "eslint .",
|
|
72
|
+
"test": "npm run typecheck && npm run lint && node --test --experimental-test-coverage --test-coverage-include=\"src/**/*.ts\" --test-coverage-exclude=\"src/**/*.spec.ts\" --test-coverage-lines=90 \"src/**/*.spec.ts\"",
|
|
73
|
+
"prepack": "npm run build",
|
|
74
|
+
"prepublishOnly": "npm test && npm run build"
|
|
75
|
+
},
|
|
76
|
+
"dependencies": {
|
|
77
|
+
"@nestjs/passport": "11.0.5",
|
|
78
|
+
"bcryptjs": "3.0.3",
|
|
79
|
+
"jose": "6.2.12",
|
|
80
|
+
"passport": "0.7.0",
|
|
81
|
+
"passport-google-oauth20": "2.0.0",
|
|
82
|
+
"passport-local": "1.0.0"
|
|
83
|
+
},
|
|
84
|
+
"peerDependencies": {
|
|
85
|
+
"@nestjs/common": "^11.0.0",
|
|
86
|
+
"@nestjs/core": "^11.0.0",
|
|
87
|
+
"react": ">=18",
|
|
88
|
+
"reflect-metadata": "^0.1.12 || ^0.2.0"
|
|
89
|
+
},
|
|
90
|
+
"peerDependenciesMeta": {
|
|
91
|
+
"@nestjs/common": {
|
|
92
|
+
"optional": true
|
|
93
|
+
},
|
|
94
|
+
"@nestjs/core": {
|
|
95
|
+
"optional": true
|
|
96
|
+
},
|
|
97
|
+
"react": {
|
|
98
|
+
"optional": true
|
|
99
|
+
},
|
|
100
|
+
"reflect-metadata": {
|
|
101
|
+
"optional": true
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
"devDependencies": {
|
|
105
|
+
"@eslint/js": "10.0.1",
|
|
106
|
+
"@nestjs/common": "11.2.5",
|
|
107
|
+
"@nestjs/core": "11.2.5",
|
|
108
|
+
"@nestjs/testing": "11.2.5",
|
|
109
|
+
"@types/express": "4.17.25",
|
|
110
|
+
"@types/node": "22.20.4",
|
|
111
|
+
"@types/passport-google-oauth20": "2.0.17",
|
|
112
|
+
"@types/passport-local": "1.0.38",
|
|
113
|
+
"@types/react": "19.3.0",
|
|
114
|
+
"@types/react-dom": "19.3.0",
|
|
115
|
+
"eslint": "10.13.0",
|
|
116
|
+
"jiti": "2.7.0",
|
|
117
|
+
"react": "19.2.3",
|
|
118
|
+
"react-dom": "19.2.3",
|
|
119
|
+
"reflect-metadata": "0.2.2",
|
|
120
|
+
"rxjs": "7.8.2",
|
|
121
|
+
"typescript": "5.9.3",
|
|
122
|
+
"typescript-eslint": "8.72.0"
|
|
123
|
+
},
|
|
124
|
+
"publishConfig": {
|
|
125
|
+
"access": "public",
|
|
126
|
+
"provenance": true
|
|
127
|
+
}
|
|
128
|
+
}
|