rst-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 +18 -0
- package/CODE_OF_CONDUCT.md +73 -0
- package/CONTRIBUTING.md +35 -0
- package/LICENSE +21 -0
- package/README.md +307 -2
- package/SECURITY.md +13 -0
- package/dist/accounts.d.ts +41 -0
- package/dist/accounts.d.ts.map +1 -0
- package/dist/accounts.js +169 -0
- package/dist/accounts.js.map +1 -0
- package/dist/config.d.ts +63 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +196 -0
- package/dist/config.js.map +1 -0
- package/dist/errors.d.ts +16 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +57 -0
- package/dist/errors.js.map +1 -0
- package/dist/express/index.d.ts +74 -0
- package/dist/express/index.d.ts.map +1 -0
- package/dist/express/index.js +185 -0
- package/dist/express/index.js.map +1 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +27 -0
- package/dist/index.js.map +1 -0
- package/dist/memory-refresh-token-store.d.ts +15 -0
- package/dist/memory-refresh-token-store.d.ts.map +1 -0
- package/dist/memory-refresh-token-store.js +75 -0
- package/dist/memory-refresh-token-store.js.map +1 -0
- package/dist/password.d.ts +7 -0
- package/dist/password.d.ts.map +1 -0
- package/dist/password.js +36 -0
- package/dist/password.js.map +1 -0
- package/dist/refresh-store.d.ts +48 -0
- package/dist/refresh-store.d.ts.map +1 -0
- package/dist/refresh-store.js +2 -0
- package/dist/refresh-store.js.map +1 -0
- package/dist/refresh.d.ts +27 -0
- package/dist/refresh.d.ts.map +1 -0
- package/dist/refresh.js +93 -0
- package/dist/refresh.js.map +1 -0
- package/dist/testing/index.d.ts +3 -0
- package/dist/testing/index.d.ts.map +1 -0
- package/dist/testing/index.js +2 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/testing/refresh-store-conformance.d.ts +11 -0
- package/dist/testing/refresh-store-conformance.d.ts.map +1 -0
- package/dist/testing/refresh-store-conformance.js +110 -0
- package/dist/testing/refresh-store-conformance.js.map +1 -0
- package/dist/tokens.d.ts +15 -0
- package/dist/tokens.d.ts.map +1 -0
- package/dist/tokens.js +90 -0
- package/dist/tokens.js.map +1 -0
- package/dist/validation.d.ts +7 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/validation.js +2 -0
- package/dist/validation.js.map +1 -0
- package/dist/zod/index.d.ts +5 -0
- package/dist/zod/index.d.ts.map +1 -0
- package/dist/zod/index.js +22 -0
- package/dist/zod/index.js.map +1 -0
- package/docs/access-tokens.md +15 -0
- package/docs/accounts.md +68 -0
- package/docs/before-first-release.md +31 -0
- package/docs/claims.md +42 -0
- package/docs/configuration.md +38 -0
- package/docs/cookies-vs-bearer.md +82 -0
- package/docs/error-handling.md +15 -0
- package/docs/installation.md +39 -0
- package/docs/middleware.md +24 -0
- package/docs/migration.md +17 -0
- package/docs/passwords.md +23 -0
- package/docs/refresh-tokens.md +47 -0
- package/docs/security.md +16 -0
- package/docs/validation.md +24 -0
- package/package.json +91 -4
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Installation and integration model
|
|
2
|
+
|
|
3
|
+
## Requirements and package entries
|
|
4
|
+
|
|
5
|
+
The package targets Node.js 22 and newer and ships ESM JavaScript plus TypeScript declarations. Install the core package with `npm install rst-auth`. Core imports are `rst-auth` and `rst-auth/testing`.
|
|
6
|
+
|
|
7
|
+
Express and Zod are optional peers, isolated behind `rst-auth/express` and `rst-auth/zod`:
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
npm install rst-auth express zod
|
|
11
|
+
npm install --save-dev @types/express
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Express 4 and 5 are supported by the adapter. The application should use a compatible `@types/express` version when typechecking Express routes.
|
|
15
|
+
|
|
16
|
+
## Minimal app setup
|
|
17
|
+
|
|
18
|
+
```ts
|
|
19
|
+
import { createAuth } from "rst-auth";
|
|
20
|
+
import { expressAuth } from "rst-auth/express";
|
|
21
|
+
import { zodAdapter } from "rst-auth/zod";
|
|
22
|
+
|
|
23
|
+
const auth = createAuth({
|
|
24
|
+
accessToken: {
|
|
25
|
+
key: new TextEncoder().encode(config.accessTokenSecret),
|
|
26
|
+
issuer: "service-name",
|
|
27
|
+
audience: "service-api",
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
const middleware = expressAuth(auth, { validator: zodAdapter() });
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Read secrets in the application's configuration layer and pass them into `createAuth`; the library itself does not read environment variables.
|
|
34
|
+
|
|
35
|
+
## Database integration model
|
|
36
|
+
|
|
37
|
+
The application owns user records, unique email constraints, queries, transactions, and user status rules. For repeated password/token work, optionally configure `accounts` with your model and a `findByEmail` callback; this adds `auth.accounts.register()` and `auth.accounts.login()` without choosing your database or routes. The package accepts a `RefreshTokenStore` when refresh support is enabled. Production implementations should store token hashes and make consume/revocation operations atomic and shared across application instances. The example app uses an in-memory user repository and refresh store only to demonstrate the controller boundaries; neither is suitable for deployed state.
|
|
38
|
+
|
|
39
|
+
Use `rst-auth/testing` to register the refresh-store conformance suite with your test runner. A passing suite does not replace reviewing the backend's transactional and consistency guarantees.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Express middleware
|
|
2
|
+
|
|
3
|
+
Install the Express peer and import the adapter subpath:
|
|
4
|
+
|
|
5
|
+
```ts
|
|
6
|
+
import { expressAuth, fromAny, fromCookie, fromHeader } from "rst-auth/express";
|
|
7
|
+
|
|
8
|
+
const middleware = expressAuth(auth);
|
|
9
|
+
app.get("/me", middleware.authenticate(), meController);
|
|
10
|
+
app.delete(
|
|
11
|
+
"/users/:id",
|
|
12
|
+
middleware.authenticate(),
|
|
13
|
+
middleware.authorize({ roles: ["admin"] }),
|
|
14
|
+
deleteUserController,
|
|
15
|
+
);
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
The default extractor strictly parses `Authorization: Bearer <token>`. `getToken` can be a synchronous or asynchronous extractor; helpers include `fromHeader()`, `fromCookie(name)`, and `fromAny([...])`. There is intentionally no built-in query-string extractor because URLs often enter logs, browser history, and referrers.
|
|
19
|
+
|
|
20
|
+
`authenticate({ required: false })` continues without `req.auth` when no token is present. A present malformed, invalid, expired, or revoked token still fails with 401. `authorize({ roles })` returns 401 when there is no authenticated request and 403 when role data is missing or does not match. Role claims can be stale; the optional `lookupIdentity(claims, req)` hook can load current app-specific identity data. No user lookup happens automatically.
|
|
21
|
+
|
|
22
|
+
`isRevoked` receives the Express request as its context when verification goes through this middleware. The adapter catches its own asynchronous failures on Express 4 and 5. Application controllers remain responsible for their own async error forwarding (Express 4 does not forward returned rejected promises automatically).
|
|
23
|
+
|
|
24
|
+
There is no `onExpired` grace hook. Letting an expired access token through during a grace window weakens the configured expiry boundary; clients should refresh through the application's refresh flow instead.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Migration notes
|
|
2
|
+
|
|
3
|
+
## From stateless refresh JWTs
|
|
4
|
+
|
|
5
|
+
Stateless re-issuance does not invalidate the presented token. To use this package's rotation and reuse detection, configure a persistent `RefreshTokenStore` with atomic consume before enabling refresh support. Existing JWT refresh credentials cannot be imported as active opaque-token families. Plan a session cutover: require users to authenticate again or maintain a separately designed transition window, then remove old verification after it expires. Do not label stateless re-issuance as rotation.
|
|
6
|
+
|
|
7
|
+
## From application-owned access JWTs
|
|
8
|
+
|
|
9
|
+
Map the current subject and authorization data into `generateAccessToken` claims while respecting reserved names. Configure issuer, audience, lifetime, and a pinned algorithm allowlist to match intended consumers. Review key format and key rotation before switching. The library uses `token_use: "access"`; old tokens without that claim will not verify as access tokens.
|
|
10
|
+
|
|
11
|
+
## From another password-hash format
|
|
12
|
+
|
|
13
|
+
This version supports bcryptjs only. Keep existing verification in your application during a staged migration if needed, then rehash after a successful verification. Do not downgrade a hash or silently accept bcrypt inputs above its 72-byte boundary. Plan password resets where a safe migration is not possible.
|
|
14
|
+
|
|
15
|
+
## HTTP middleware
|
|
16
|
+
|
|
17
|
+
The default source is strict `Authorization: Bearer`. If the old app used cookies or another source, configure `getToken` explicitly and add CSRF protection when using browser cookies. Validation now returns parsed data through `req.validated`; update controllers to read that property rather than expecting request fields to be replaced.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Password utilities
|
|
2
|
+
|
|
3
|
+
The package uses `bcryptjs`; it does not silently switch to another password hash. `auth.password.hash(password)` and `verify(password, hash)` validate inputs. Bcrypt only uses 72 input bytes, so this API rejects longer UTF-8 password inputs rather than allowing truncation. Malformed hashes fail safely.
|
|
4
|
+
|
|
5
|
+
```ts
|
|
6
|
+
const hash = await auth.password.hash(password);
|
|
7
|
+
const valid = await auth.password.verify(password, hash);
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
Do not return or log hashes. When a login identifier has no matching user, `verifyDummy(password)` compares against a process-local dummy hash so the unknown-user path does password-hash work too:
|
|
11
|
+
|
|
12
|
+
```ts
|
|
13
|
+
const user = await users.findByEmail(email);
|
|
14
|
+
if (!user) {
|
|
15
|
+
await auth.password.verifyDummy(password);
|
|
16
|
+
throw new AuthError("UNAUTHORIZED", "Invalid email or password");
|
|
17
|
+
}
|
|
18
|
+
if (!(await auth.password.verify(password, user.passwordHash))) {
|
|
19
|
+
throw new AuthError("UNAUTHORIZED", "Invalid email or password");
|
|
20
|
+
}
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
This helps reduce timing differences; it is not a rate limiter and does not guarantee identical response timing. The default work factor is 12. Higher factors increase offline-guessing cost and CPU time for each operation, including synchronous dummy-hash setup in `createAuth()`. Benchmark a value on production-like hardware and tune capacity controls in the application.
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# Refresh-token architecture
|
|
2
|
+
|
|
3
|
+
Refresh tokens are opaque bearer secrets, not JWTs. `auth.refresh.issue()` creates 32 random bytes with Node's `crypto.randomBytes()` and returns their base64url encoding. The configured store receives only the SHA-256 hash, never the raw token. A database leak therefore does not directly reveal usable refresh tokens.
|
|
4
|
+
|
|
5
|
+
Refresh support is opt-in. Configure `refreshToken.store`, `refreshToken.refreshTokenTtl`, and `refreshToken.absoluteLifetime` when calling `createAuth()`. If no refresh configuration is supplied, `auth.refresh` is absent. A partial configuration or missing store fails validation; the library never pretends stateless token re-issuance is rotation.
|
|
6
|
+
|
|
7
|
+
## Rotation and lifetimes
|
|
8
|
+
|
|
9
|
+
Each issue starts a family with an absolute expiry. Rotation atomically consumes the presented token, then creates a replacement in that same family. The replacement's idle expiry is `min(now + refreshTokenTtl, family absolute expiry)`. Rotation cannot extend the family past `absoluteLifetime`.
|
|
10
|
+
|
|
11
|
+
The refresh service returns a rotated refresh token, its subject and family metadata. It does not mint an access token pair: application code can use the returned subject and its own current identity/authorization data to call `auth.tokens.generateAccessToken()`. Unknown, expired, revoked, or reused tokens raise an `AuthError` and never produce a replacement token.
|
|
12
|
+
|
|
13
|
+
Presenting a previously consumed token reports `REFRESH_TOKEN_REUSED` and revokes every token in that family. `revoke(token)` revokes one active token. `revokeFamily(familyId)` revokes a family, and `revokeAllForSubject(subject)` revokes all of that subject's refresh tokens.
|
|
14
|
+
|
|
15
|
+
## Store contract and atomicity
|
|
16
|
+
|
|
17
|
+
Implement `RefreshTokenStore` with `create`, `consume`, `revokeToken`, `revokeFamily`, and `revokeAllForSubject`. Store records contain the SHA-256 token hash, family ID, subject, timestamps, and status. `consume(hash, now)` returns a discriminated result: `ok`, `not_found`, `expired`, `revoked`, or `reused`.
|
|
18
|
+
|
|
19
|
+
`consume` must be an atomic compare-and-swap. Exactly one concurrent call for an active token may return `ok`; a later or concurrent loser must observe the consumed state, report reuse, and revoke the family. A read-then-write implementation without a transaction, conditional update, or equivalent atomic primitive cannot provide this guarantee and must not claim reuse protection. Backends must also make family and subject revocation cover all currently stored members of those scopes.
|
|
20
|
+
|
|
21
|
+
Stores must preserve revoked-family tombstones through the family's absolute expiry and make `create` refuse to add a successor to a revoked family. Otherwise a rotation already in progress could create a usable child after a concurrent replay revoked the family. Concurrent refresh attempts can therefore both fail: one detects reuse, and the other is prevented from creating a successor after family revocation.
|
|
22
|
+
|
|
23
|
+
The `rst-auth/testing` subpath exports `registerRefreshTokenStoreConformanceTests()`. Register the suite with your test runner and provide a factory for a fresh store. It checks rotation behavior, replay and family revocation, expiry, explicit revocation, and concurrent consume. The suite exercises the contract but does not prove a distributed backend's atomicity under every deployment or failure mode; review the backend's transaction/consistency guarantees as well.
|
|
24
|
+
|
|
25
|
+
## Memory store
|
|
26
|
+
|
|
27
|
+
`MemoryRefreshTokenStore` is only for tests and local examples. It provides atomic consume within one JavaScript process by changing state before its async method yields. It is not safe across processes, restarts, or multiple application instances. Use a persistent store with an atomic consume operation for deployed services.
|
|
28
|
+
|
|
29
|
+
## Example
|
|
30
|
+
|
|
31
|
+
```ts
|
|
32
|
+
import { createAuth, MemoryRefreshTokenStore } from "rst-auth";
|
|
33
|
+
|
|
34
|
+
const auth = createAuth({
|
|
35
|
+
accessToken: { key: new TextEncoder().encode(process.env.ACCESS_TOKEN_KEY!) },
|
|
36
|
+
refreshToken: {
|
|
37
|
+
store: new MemoryRefreshTokenStore(), // local example only
|
|
38
|
+
refreshTokenTtl: 60 * 60 * 24 * 7,
|
|
39
|
+
absoluteLifetime: 60 * 60 * 24 * 30,
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
const refresh = await auth.refresh!.issue({ subject: "user-123" });
|
|
44
|
+
const access = await auth.tokens.generateAccessToken({ sub: refresh.subject });
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
The sample reads the secret in application configuration; the library itself never reads `process.env`. A production application should inject a persistent store and keep raw refresh tokens out of logs and persistent records.
|
package/docs/security.md
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Security responsibilities
|
|
2
|
+
|
|
3
|
+
This package supplies auth primitives; it does not replace application security controls or an independent security review. Keep dependencies current, protect signing keys, avoid logging credentials/tokens/hashes, validate inputs, and review the threat model of the deployed refresh store.
|
|
4
|
+
|
|
5
|
+
The application remains responsible for:
|
|
6
|
+
|
|
7
|
+
- **CORS:** allow only required origins and methods. CORS is not authentication and is not a CSRF defense by itself.
|
|
8
|
+
- **Rate limiting:** limit login, signup, refresh, password reset, and other sensitive endpoints by appropriate identifiers and network context.
|
|
9
|
+
- **CSRF:** browser cookies are attached automatically in some cross-site contexts. Use CSRF defenses appropriate to the routes and cookie policy; SameSite helps but is not a universal replacement. See [cookies versus Bearer](cookies-vs-bearer.md).
|
|
10
|
+
- **Account lockout and abuse policy:** define throttling, lockout, recovery, and monitoring without creating trivial denial-of-service paths.
|
|
11
|
+
- **Password reset:** design single-use, expiring, securely stored reset credentials and account notification yourself.
|
|
12
|
+
- **Email verification, account status, user lookup, and current permissions:** a signed token alone does not show that an account remains active or current.
|
|
13
|
+
- **Signing key lifecycle, backup, rotation, and deployment secret access.**
|
|
14
|
+
- **Refresh persistence:** provide a shared store whose consume and family revocation operations are atomic for your database/consistency model. Memory store is test/local only.
|
|
15
|
+
|
|
16
|
+
`isRevoked` can provide access-token denylist/session checks, at the cost of making verification stateful and dependent on that check. A valid JWT without current lookup may outlive a deleted or disabled user until its expiry. Choose access-token lifetime and current authorization checks accordingly.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Request validation
|
|
2
|
+
|
|
3
|
+
The Express adapter accepts a framework-independent `ValidatorAdapter`. `rst-auth/zod` provides `zodAdapter()` and supports async Zod parsing. Apps can implement the same small interface for another validator.
|
|
4
|
+
|
|
5
|
+
```ts
|
|
6
|
+
const middleware = expressAuth(auth, { validator: zodAdapter() });
|
|
7
|
+
app.post(
|
|
8
|
+
"/users/:id",
|
|
9
|
+
middleware.validate({
|
|
10
|
+
body: bodySchema,
|
|
11
|
+
params: paramsSchema,
|
|
12
|
+
query: querySchema,
|
|
13
|
+
}),
|
|
14
|
+
(req, res) => {
|
|
15
|
+
const body = req.validated?.body;
|
|
16
|
+
const params = req.validated?.params;
|
|
17
|
+
res.json({ body, params });
|
|
18
|
+
},
|
|
19
|
+
);
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Only configured targets are parsed. Parsed data is assigned to `req.validated.body`, `.params`, and `.query`; source request properties are not replaced. This is important for Express 5, whose `req.query` is a getter. A validation failure becomes `VALIDATION_ERROR` (HTTP 400) by default. Provide `formatError` on the validation schema options for target-specific mapping, or configure the Express `errorFormatter` for the public response shape. Formatters should emit only information safe for clients.
|
|
23
|
+
|
|
24
|
+
Zod output types are inferred by Zod itself. For a schema-specific request view, the Express subpath exports `ValidatedRequest<TSchemas>`; global request augmentation stays broad so different routes can use different schemas.
|
package/package.json
CHANGED
|
@@ -1,6 +1,93 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rst-auth",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "A modular authentication toolkit for Node.js applications",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"engines": {
|
|
8
|
+
"node": ">=22"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist",
|
|
12
|
+
"docs",
|
|
13
|
+
"README.md",
|
|
14
|
+
"LICENSE",
|
|
15
|
+
"CHANGELOG.md",
|
|
16
|
+
"CONTRIBUTING.md",
|
|
17
|
+
"CODE_OF_CONDUCT.md",
|
|
18
|
+
"SECURITY.md"
|
|
19
|
+
],
|
|
20
|
+
"main": "./dist/index.js",
|
|
21
|
+
"types": "./dist/index.d.ts",
|
|
22
|
+
"exports": {
|
|
23
|
+
".": {
|
|
24
|
+
"types": "./dist/index.d.ts",
|
|
25
|
+
"import": "./dist/index.js"
|
|
26
|
+
},
|
|
27
|
+
"./testing": {
|
|
28
|
+
"types": "./dist/testing/index.d.ts",
|
|
29
|
+
"import": "./dist/testing/index.js"
|
|
30
|
+
},
|
|
31
|
+
"./express": {
|
|
32
|
+
"types": "./dist/express/index.d.ts",
|
|
33
|
+
"import": "./dist/express/index.js"
|
|
34
|
+
},
|
|
35
|
+
"./zod": {
|
|
36
|
+
"types": "./dist/zod/index.d.ts",
|
|
37
|
+
"import": "./dist/zod/index.js"
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
"peerDependencies": {
|
|
41
|
+
"@types/express": ">=4.17.21 <6",
|
|
42
|
+
"express": ">=4.18 <6",
|
|
43
|
+
"zod": ">=3.22 <5"
|
|
44
|
+
},
|
|
45
|
+
"peerDependenciesMeta": {
|
|
46
|
+
"@types/express": {
|
|
47
|
+
"optional": true
|
|
48
|
+
},
|
|
49
|
+
"express": {
|
|
50
|
+
"optional": true
|
|
51
|
+
},
|
|
52
|
+
"zod": {
|
|
53
|
+
"optional": true
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
"scripts": {
|
|
57
|
+
"build": "tsc -p tsconfig.build.json",
|
|
58
|
+
"test": "vitest run",
|
|
59
|
+
"lint": "eslint .",
|
|
60
|
+
"typecheck": "tsc --noEmit -p tsconfig.json",
|
|
61
|
+
"format:check": "prettier --check .",
|
|
62
|
+
"example:install": "node scripts/install-example.mjs"
|
|
63
|
+
},
|
|
64
|
+
"dependencies": {
|
|
65
|
+
"bcryptjs": "^3.0.2",
|
|
66
|
+
"jose": "^6.1.0"
|
|
67
|
+
},
|
|
68
|
+
"devDependencies": {
|
|
69
|
+
"@eslint/js": "^10.0.1",
|
|
70
|
+
"@types/bcryptjs": "^2.4.6",
|
|
71
|
+
"@types/express": "^5.0.6",
|
|
72
|
+
"@types/node": "^24.10.0",
|
|
73
|
+
"@types/supertest": "^7.2.1",
|
|
74
|
+
"eslint": "^10.12.0",
|
|
75
|
+
"express": "^5.3.0",
|
|
76
|
+
"express4": "npm:express@^4.21.2",
|
|
77
|
+
"globals": "^16.5.0",
|
|
78
|
+
"prettier": "^3.6.2",
|
|
79
|
+
"supertest": "^7.3.1",
|
|
80
|
+
"typescript": "^5.9.3",
|
|
81
|
+
"typescript-eslint": "^8.46.0",
|
|
82
|
+
"vitest": "^5.0.5",
|
|
83
|
+
"zod": "^4.6.5"
|
|
84
|
+
},
|
|
85
|
+
"repository": {
|
|
86
|
+
"type": "git",
|
|
87
|
+
"url": "https://github.com/sayedrisat/rst-auth.git"
|
|
88
|
+
},
|
|
89
|
+
"homepage": "https://sayedrisat.github.io/rst-auth/",
|
|
90
|
+
"bugs": {
|
|
91
|
+
"url": "https://github.com/sayedrisat/rst-auth/issues"
|
|
92
|
+
}
|
|
93
|
+
}
|