147 lines
5.2 KiB
Markdown
147 lines
5.2 KiB
Markdown
# Authentication & User Management
|
|
|
|
Complete auth system with registration, login, sessions, and verification flows.
|
|
|
|
## Architecture
|
|
|
|
```
|
|
Frontend (AuthProvider) → HTTP → Auth Handlers → AuthService → Repositories (Memory | Postgres)
|
|
```
|
|
|
|
- **pkg/auth/** — JWT validation, middleware, password hashing, session checking (shared)
|
|
- **service/internal/domain/** — User, Session, AuthCode domain models
|
|
- **service/internal/port/** — Repository interfaces (UserRepository, SessionRepository, AuthCodeRepository)
|
|
- **service/internal/adapter/memory/** — In-memory implementations for standalone dev
|
|
- **service/internal/adapter/postgres/** — PostgreSQL/CockroachDB implementations for production
|
|
- **service/internal/service/auth.go** — Business logic (AuthService)
|
|
- **service/internal/api/handlers/auth.go** — Core HTTP handlers (login, register, profile)
|
|
- **service/internal/api/handlers/auth_flows.go** — Flow handlers (OTP, magic link, sessions, reset)
|
|
|
|
## Standalone Mode (No DATABASE_URL)
|
|
|
|
When `DATABASE_URL` is not set, the service runs with in-memory adapters:
|
|
- Two demo users seeded: `test@example.com` / `Password123`, `admin@example.com` / `Admin1234`
|
|
- Auth codes (OTP, magic links, reset tokens) logged to stdout (no notify/email needed)
|
|
- Sessions stored in memory (lost on restart)
|
|
- No external dependencies required
|
|
|
|
## Token Lifecycle
|
|
|
|
- **Access token:** 15 minutes, JWT with embedded session ID (`sid` claim)
|
|
- **Refresh:** POST `/auth/refresh` with valid token returns new token (same session)
|
|
- **Session:** 30-day lifetime, tracked in sessions table
|
|
- **Revocation:** Revoking a session invalidates all tokens for that session
|
|
|
|
## Environment Variables
|
|
|
|
| Variable | Default | Description |
|
|
|----------|---------|-------------|
|
|
| `JWT_SECRET` | `""` | Secret for signing JWT tokens |
|
|
| `REGISTRATION_ENABLED` | `true` | Allow new user registration |
|
|
| `DATABASE_URL` | `""` | If set, use Postgres repos; otherwise in-memory |
|
|
| `NOTIFY_URL` | `""` | Notify service URL. If set, emails sent via notify; otherwise logged to stdout |
|
|
| `NOTIFY_API_KEY` | `""` | Per-project notify send key (`notify_send_xxx`) |
|
|
| `NOTIFY_HOST` | `""` | Sending domain (e.g. `myapp.threesix.ai`) |
|
|
| `NOTIFY_FROM` | `noreply@{project}.com` | Registered sender address |
|
|
|
|
## Auth Flows
|
|
|
|
### Password Login
|
|
```
|
|
POST /auth/login { email, password } → { token, user }
|
|
```
|
|
|
|
### Registration
|
|
```
|
|
POST /auth/register { email, password, name } → { token, user }
|
|
```
|
|
|
|
### OTP Login
|
|
```
|
|
POST /auth/otp/send { email } → 200 (code logged to stdout in dev)
|
|
POST /auth/otp/verify { email, code } → { token, user }
|
|
```
|
|
|
|
### Magic Link
|
|
```
|
|
POST /auth/magic-link { email } → 200 (token logged to stdout in dev)
|
|
POST /auth/magic-link/verify { email, token } → { token, user }
|
|
```
|
|
|
|
### Password Reset
|
|
```
|
|
POST /auth/forgot-password { email } → 200 (token logged to stdout in dev)
|
|
POST /auth/reset-password { email, token, newPassword } → 200
|
|
```
|
|
|
|
### Email Verification (requires auth)
|
|
```
|
|
POST /auth/verify-email/send → 200 (code logged to stdout in dev)
|
|
POST /auth/verify-email { code } → 200
|
|
```
|
|
|
|
### Session Management (requires auth)
|
|
```
|
|
GET /auth/sessions → [{ id, deviceLabel, ipAddress, lastActiveAt, isCurrent }]
|
|
DELETE /auth/sessions/{id} → 204
|
|
DELETE /auth/sessions → 204 (revoke all except current)
|
|
```
|
|
|
|
### Profile (requires auth)
|
|
```
|
|
GET /auth/me → { user }
|
|
PUT /auth/me { name, avatarUrl } → { user }
|
|
POST /auth/change-password { currentPassword, newPassword } → 200
|
|
POST /auth/logout → 204
|
|
```
|
|
|
|
## Frontend Integration
|
|
|
|
The `@persona-community-5/auth` package provides `AuthProvider` and `useAuth()` hook:
|
|
|
|
```tsx
|
|
// In App.tsx
|
|
<AuthProvider authBaseUrl={`${apiBaseUrl}/api/service-name`}>
|
|
<App />
|
|
</AuthProvider>
|
|
|
|
// In components
|
|
const { user, login, register, logout, sendOTP, loginWithOTP } = useAuth();
|
|
```
|
|
|
|
Auto-refresh schedules token renewal at 80% of token lifetime.
|
|
|
|
## Adding Session Revocation Middleware
|
|
|
|
To enforce session revocation on every request (opt-in):
|
|
|
|
```go
|
|
import "git.threesix.ai/jordan/persona-community-5/pkg/auth"
|
|
|
|
checker := func(ctx context.Context, sid string) (bool, error) {
|
|
session, err := sessionRepo.Get(ctx, domain.SessionID(sid))
|
|
if err != nil { return false, nil }
|
|
return session.IsActive(), nil
|
|
}
|
|
|
|
r.Use(auth.SessionCheck(checker))
|
|
```
|
|
|
|
## Password Requirements
|
|
|
|
- Minimum 8 characters, maximum 72 (bcrypt limit)
|
|
- Must contain uppercase, lowercase, and digit
|
|
- Hashed with bcrypt cost 12
|
|
|
|
## Database Tables
|
|
|
|
When `DATABASE_URL` is set, these tables are auto-created:
|
|
- `users` — Core identity (email, name, status)
|
|
- `user_passwords` — Bcrypt hashes (separate for OAuth-only users)
|
|
- `sessions` — Login sessions with IP/device tracking
|
|
- `auth_codes` — OTP, magic link, reset, and verification codes
|
|
- `user_roles` — Many-to-many user roles
|
|
- `oauth_connections` — Schema placeholder for future OAuth provider links (table exists but no handlers/adapters yet)
|
|
|
|
> **Note:** The `oauth_connections` table is created by the migration but has no corresponding handlers, service methods, or adapters. It's a schema placeholder — implementing OAuth requires building the full handler → service → adapter chain. See the composable monorepo templates guide for adding new auth providers.
|