@brika/auth 0.1.1
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 +207 -0
- package/package.json +50 -0
- package/src/__tests__/AuthClient.test.ts +736 -0
- package/src/__tests__/AuthService.test.ts +140 -0
- package/src/__tests__/ScopeService.test.ts +156 -0
- package/src/__tests__/SessionService.test.ts +311 -0
- package/src/__tests__/UserService-avatar.test.ts +277 -0
- package/src/__tests__/UserService.test.ts +223 -0
- package/src/__tests__/canAccess.test.ts +166 -0
- package/src/__tests__/disabledScopes.test.ts +101 -0
- package/src/__tests__/middleware.test.ts +190 -0
- package/src/__tests__/plugin.test.ts +78 -0
- package/src/__tests__/requireSession.test.ts +78 -0
- package/src/__tests__/routes-auth.test.ts +248 -0
- package/src/__tests__/routes-profile.test.ts +403 -0
- package/src/__tests__/routes-scopes.test.ts +64 -0
- package/src/__tests__/routes-sessions.test.ts +235 -0
- package/src/__tests__/routes-users.test.ts +477 -0
- package/src/__tests__/serveImage.test.ts +277 -0
- package/src/__tests__/setup.test.ts +270 -0
- package/src/__tests__/verifyToken.test.ts +219 -0
- package/src/client/AuthClient.ts +312 -0
- package/src/client/http-client.ts +84 -0
- package/src/client/index.ts +19 -0
- package/src/config.ts +82 -0
- package/src/constants.ts +10 -0
- package/src/index.ts +16 -0
- package/src/lib/define-roles.ts +35 -0
- package/src/lib/define-scopes.ts +48 -0
- package/src/middleware/canAccess.ts +126 -0
- package/src/middleware/index.ts +13 -0
- package/src/middleware/requireAuth.ts +35 -0
- package/src/middleware/requireScope.ts +46 -0
- package/src/middleware/verifyToken.ts +52 -0
- package/src/plugin.ts +86 -0
- package/src/react/AuthProvider.tsx +105 -0
- package/src/react/hooks.ts +128 -0
- package/src/react/index.ts +51 -0
- package/src/react/withScopeGuard.tsx +73 -0
- package/src/roles.ts +40 -0
- package/src/schemas.ts +112 -0
- package/src/scopes.ts +60 -0
- package/src/server/index.ts +44 -0
- package/src/server/requireSession.ts +44 -0
- package/src/server/routes/auth.ts +102 -0
- package/src/server/routes/cookie.ts +7 -0
- package/src/server/routes/index.ts +32 -0
- package/src/server/routes/profile.ts +162 -0
- package/src/server/routes/scopes.ts +22 -0
- package/src/server/routes/sessions.ts +68 -0
- package/src/server/routes/setup.ts +50 -0
- package/src/server/routes/users.ts +175 -0
- package/src/server/serveImage.ts +91 -0
- package/src/services/AuthService.ts +80 -0
- package/src/services/ScopeService.ts +94 -0
- package/src/services/SessionService.ts +245 -0
- package/src/services/UserService.ts +245 -0
- package/src/setup.ts +99 -0
- package/src/tanstack/index.ts +15 -0
- package/src/tanstack/routeBuilder.ts +311 -0
- package/src/types.ts +118 -0
- package/tsconfig.json +8 -0
package/README.md
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
# @brika/auth
|
|
2
|
+
|
|
3
|
+
Complete authentication system for Brika with cookie-based sessions, scope-based RBAC, and type-safe route protection.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Cookie-Based Sessions** — HttpOnly, Secure, SameSite=Lax cookies with server-side SQLite session store
|
|
8
|
+
- **User Management** — CRUD, bcrypt password hashing (cost 12), avatar upload with image validation
|
|
9
|
+
- **Scope-Based RBAC** — Fine-grained permissions with role defaults and per-user overrides
|
|
10
|
+
- **Type-Safe Route Protection** — Single-source-of-truth route declarations with TanStack Router integration
|
|
11
|
+
- **Security Hardened** — Rate limiting, session limits, deactivated user checks, sliding expiration
|
|
12
|
+
|
|
13
|
+
## Quick Start
|
|
14
|
+
|
|
15
|
+
### Backend: Hub Integration
|
|
16
|
+
|
|
17
|
+
```typescript
|
|
18
|
+
import { auth } from '@brika/auth/server';
|
|
19
|
+
|
|
20
|
+
await bootstrap()
|
|
21
|
+
.use(auth({ dataDir, server: inject(ApiServer) }))
|
|
22
|
+
.start();
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### Frontend: React Setup
|
|
26
|
+
|
|
27
|
+
```typescript
|
|
28
|
+
import { AuthProvider, useAuth, useCanAccess } from '@brika/auth/react';
|
|
29
|
+
import { Scope } from '@brika/auth';
|
|
30
|
+
|
|
31
|
+
function App() {
|
|
32
|
+
return (
|
|
33
|
+
<AuthProvider>
|
|
34
|
+
<Dashboard />
|
|
35
|
+
</AuthProvider>
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function Dashboard() {
|
|
40
|
+
const { user, logout } = useAuth();
|
|
41
|
+
const canEdit = useCanAccess(Scope.WORKFLOW_WRITE);
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<>
|
|
45
|
+
<h1>Hello, {user?.name}!</h1>
|
|
46
|
+
{canEdit && <EditButton />}
|
|
47
|
+
<button onClick={logout}>Logout</button>
|
|
48
|
+
</>
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## HTTP API
|
|
54
|
+
|
|
55
|
+
### POST /api/auth/login
|
|
56
|
+
|
|
57
|
+
Rate limited: 5 requests per 60 seconds per IP.
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
POST /api/auth/login
|
|
61
|
+
Content-Type: application/json
|
|
62
|
+
|
|
63
|
+
{ "email": "user@example.com", "password": "SecurePassword123!" }
|
|
64
|
+
|
|
65
|
+
# Response (200) — session cookie set automatically
|
|
66
|
+
{ "user": { "id": "...", "email": "...", "name": "...", "role": "user" } }
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### POST /api/auth/logout
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
POST /api/auth/logout
|
|
73
|
+
# Session cookie cleared
|
|
74
|
+
{ "ok": true }
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### GET /api/auth/session
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
GET /api/auth/session
|
|
81
|
+
# Returns current session info
|
|
82
|
+
{ "user": { ... }, "scopes": ["workflow:read", "board:read"] }
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Profile Endpoints
|
|
86
|
+
|
|
87
|
+
| Method | Path | Auth | Description |
|
|
88
|
+
|--------|------|------|-------------|
|
|
89
|
+
| PUT | `/api/auth/profile` | Required | Update name |
|
|
90
|
+
| PUT | `/api/auth/profile/password` | Required + Rate limited | Change password |
|
|
91
|
+
| PUT | `/api/auth/profile/avatar` | Required | Upload avatar (PNG/JPEG/WebP, max 5MB) |
|
|
92
|
+
| DELETE | `/api/auth/profile/avatar` | Required | Remove avatar |
|
|
93
|
+
| GET | `/api/auth/avatar/:userId` | Public | Serve avatar image |
|
|
94
|
+
|
|
95
|
+
### Admin Endpoints (requires `admin:*` scope)
|
|
96
|
+
|
|
97
|
+
| Method | Path | Description |
|
|
98
|
+
|--------|------|-------------|
|
|
99
|
+
| GET | `/api/users` | List all users |
|
|
100
|
+
| POST | `/api/users` | Create user |
|
|
101
|
+
| GET | `/api/users/:id` | Get user (admin or self) |
|
|
102
|
+
| PUT | `/api/users/:id` | Update user |
|
|
103
|
+
| PUT | `/api/users/:id/password` | Reset password (revokes all sessions) |
|
|
104
|
+
| DELETE | `/api/users/:id` | Delete user (revokes all sessions) |
|
|
105
|
+
|
|
106
|
+
## Security
|
|
107
|
+
|
|
108
|
+
### Authentication Flow
|
|
109
|
+
|
|
110
|
+
1. Login sends credentials over HTTPS, receives `Set-Cookie` with session token
|
|
111
|
+
2. Session token is `HttpOnly; Secure; SameSite=Lax; Path=/api`
|
|
112
|
+
3. Server stores SHA-256 hash of token in SQLite (raw token never stored)
|
|
113
|
+
4. Token entropy: 256-bit CSPRNG (`crypto.randomBytes(32)`)
|
|
114
|
+
|
|
115
|
+
### Protections
|
|
116
|
+
|
|
117
|
+
| Threat | Protection |
|
|
118
|
+
|--------|-----------|
|
|
119
|
+
| Brute-force login | Rate limiting (5/min per IP via sliding window counter) |
|
|
120
|
+
| Brute-force password change | Rate limiting (10/15min per IP) |
|
|
121
|
+
| Session hijacking | HttpOnly + Secure + SameSite=Lax cookies |
|
|
122
|
+
| Deactivated user access | Checked at login AND on every session validation |
|
|
123
|
+
| Password cracking | bcrypt cost 12, max 72 chars (bcrypt limit) |
|
|
124
|
+
| Session accumulation | Per-user session limit (default: 10), automatic cleanup every 6 hours |
|
|
125
|
+
| Memory exhaustion | Rate limiter store capped at 10K keys with automatic eviction |
|
|
126
|
+
| IP spoofing | Server uses socket IP, strips client-supplied proxy headers |
|
|
127
|
+
| Scope enumeration | 403 responses don't reveal user's actual scopes |
|
|
128
|
+
| Avatar abuse | Magic byte validation (PNG/JPEG/WebP), 5MB size limit |
|
|
129
|
+
| DB file access | Restrictive file permissions (0o600) |
|
|
130
|
+
|
|
131
|
+
### Session Lifecycle
|
|
132
|
+
|
|
133
|
+
- **Sliding expiration**: TTL resets on each authenticated request
|
|
134
|
+
- **Password change**: All sessions revoked (self-change and admin reset)
|
|
135
|
+
- **User deletion**: All sessions explicitly revoked before deletion
|
|
136
|
+
- **Deactivation**: Sessions immediately invalid (checked via JOIN in validation query)
|
|
137
|
+
- **Expired session cleanup**: Runs on startup + every 6 hours
|
|
138
|
+
|
|
139
|
+
## Configuration
|
|
140
|
+
|
|
141
|
+
```typescript
|
|
142
|
+
auth({
|
|
143
|
+
dataDir: '~/.brika',
|
|
144
|
+
server: inject(ApiServer),
|
|
145
|
+
config: {
|
|
146
|
+
session: {
|
|
147
|
+
ttl: 604800, // 7 days (default)
|
|
148
|
+
cookieName: 'brika_session',
|
|
149
|
+
maxPerUser: 10, // Max concurrent sessions per user
|
|
150
|
+
},
|
|
151
|
+
password: {
|
|
152
|
+
minLength: 8,
|
|
153
|
+
requireUppercase: true,
|
|
154
|
+
requireNumbers: true,
|
|
155
|
+
requireSpecial: true,
|
|
156
|
+
},
|
|
157
|
+
},
|
|
158
|
+
});
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
## Package Structure
|
|
162
|
+
|
|
163
|
+
```
|
|
164
|
+
@brika/auth
|
|
165
|
+
├── Types & Constants — User, Session, Role, Scope, ROLE_SCOPES
|
|
166
|
+
├── @brika/auth/server
|
|
167
|
+
│ ├── AuthService — Login, logout
|
|
168
|
+
│ ├── UserService — User CRUD, passwords, avatars
|
|
169
|
+
│ ├── SessionService — Session create/validate/revoke, cleanup
|
|
170
|
+
│ ├── ScopeService — Permission checks
|
|
171
|
+
│ ├── Middleware — verifyToken, requireAuth, requireScope, canAccess
|
|
172
|
+
│ ├── Routes — HTTP API endpoints
|
|
173
|
+
│ └── Plugin — Bootstrap integration with session cleanup scheduler
|
|
174
|
+
├── @brika/auth/client
|
|
175
|
+
│ └── AuthClient — HTTP client for browser
|
|
176
|
+
├── @brika/auth/react
|
|
177
|
+
│ ├── AuthProvider — Context with clearSession/refreshSession
|
|
178
|
+
│ ├── Hooks — useAuth, useCanAccess, useCanAccessAll, etc.
|
|
179
|
+
│ └── HOCs — withScopeGuard, withOptionalScope
|
|
180
|
+
└── @brika/auth/tanstack
|
|
181
|
+
└── createProtectedRoutes — Type-safe route builder with scope guards
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
## Roles & Scopes
|
|
185
|
+
|
|
186
|
+
| Role | Default Scopes |
|
|
187
|
+
|------|---------------|
|
|
188
|
+
| `admin` | All scopes (`admin:*`) |
|
|
189
|
+
| `user` | workflow:*, board:*, plugin:read, settings:read |
|
|
190
|
+
| `guest` | workflow:read, board:read, plugin:read |
|
|
191
|
+
|
|
192
|
+
## CLI Commands
|
|
193
|
+
|
|
194
|
+
All commands are interactive (prompts for input):
|
|
195
|
+
|
|
196
|
+
```bash
|
|
197
|
+
brika auth user add # Add a new user (email, name, role, password)
|
|
198
|
+
brika auth user list # List all users
|
|
199
|
+
brika auth user edit # Edit a user (name, role, status, password)
|
|
200
|
+
brika auth user delete # Delete a user
|
|
201
|
+
|
|
202
|
+
brika auth token create # Create an API token (user, scopes, expiration)
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
## License
|
|
206
|
+
|
|
207
|
+
MIT
|
package/package.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@brika/auth",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Authentication & authorization system for Brika",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/brikalabs/brika",
|
|
10
|
+
"directory": "packages/auth"
|
|
11
|
+
},
|
|
12
|
+
"exports": {
|
|
13
|
+
".": "./src/index.ts",
|
|
14
|
+
"./server": "./src/server/index.ts",
|
|
15
|
+
"./client": "./src/client/index.ts",
|
|
16
|
+
"./react": "./src/react/index.ts",
|
|
17
|
+
"./tanstack": "./src/tanstack/index.ts"
|
|
18
|
+
},
|
|
19
|
+
"scripts": {
|
|
20
|
+
"typecheck": "tsgo --noEmit",
|
|
21
|
+
"test": "bun test",
|
|
22
|
+
"test:watch": "bun test --watch"
|
|
23
|
+
},
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"@brika/di": "0.3.1",
|
|
26
|
+
"@brika/photon": "0.3.1",
|
|
27
|
+
"@brika/router": "0.3.1",
|
|
28
|
+
"bcryptjs": "^2.4.3",
|
|
29
|
+
"zod": "^4.3.4"
|
|
30
|
+
},
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"@types/bcryptjs": "^2.4.6",
|
|
33
|
+
"@types/bun": "latest",
|
|
34
|
+
"@types/react": "^19.2.0",
|
|
35
|
+
"react": "^19.2.0",
|
|
36
|
+
"@tanstack/react-router": "^1.163.2"
|
|
37
|
+
},
|
|
38
|
+
"peerDependencies": {
|
|
39
|
+
"react": "^18 || ^19",
|
|
40
|
+
"@tanstack/react-router": ">=1.29.0"
|
|
41
|
+
},
|
|
42
|
+
"peerDependenciesMeta": {
|
|
43
|
+
"react": {
|
|
44
|
+
"optional": true
|
|
45
|
+
},
|
|
46
|
+
"@tanstack/react-router": {
|
|
47
|
+
"optional": true
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|