Authentication & RBAC
JWT Authentication
Planner uses JSON Web Tokens for stateless authentication:
- Access token: 15-minute lifetime, sent in
Authorization: Bearer <token>header - Refresh token: 7-day lifetime, used to obtain new access tokens
Authentication Flow
POST /api/auth/register— create account (firstName, lastName, email, password)POST /api/auth/verify-email— verify email with token from emailPOST /api/auth/login— get access + refresh tokens (requires verified email)- All subsequent requests include
Authorization: Bearer <access_token> POST /api/auth/refresh— exchange refresh token for new access tokenPOST /api/auth/logout— revoke all refresh tokens
Email Verification
New users must verify their email before first login:
POST /api/auth/register— creates user, sends verification email (does NOT return tokens)POST /api/auth/verify-email— validates token, marks email as verifiedPOST /api/auth/resend-verification— resends verification email (rate limited: 3/min)POST /api/auth/loginandPOST /api/auth/refresh— throwEMAIL_NOT_VERIFIEDif email not verified- Existing users are backfilled via seed (
prisma/seed.tssetsemailVerifiedAt = now())
Rate Limiting
Per-endpoint rate limits protect against brute-force attacks:
| Endpoint | Limit |
|---|---|
| Register | 5 requests/minute |
| Login | 10 requests/minute |
| Refresh | 20 requests/minute |
| Forgot-password | 3 requests/minute |
| Reset-password | 5 requests/minute |
| Verify-email | 5 requests/minute |
| Resend-verification | 3 requests/minute |
Global rate limit: 60 requests/minute (stored in Redis in production).
Account Lockout
After 5 failed login attempts, the account is locked for 15 minutes via Redis (lockout:<email> key). Generic error messages (INVALID_CREDENTIALS) prevent user enumeration.
Password Policy
- Minimum 8 characters, maximum 128
- Requires: uppercase letter, lowercase letter, digit, special character
- Hashed with bcrypt (cost factor 10)
- Password change invalidates all existing refresh tokens
- Password reset also invalidates all refresh tokens
Refresh Token Rotation
- Refresh tokens stored as SHA-256 hash (not plaintext)
- On each refresh: old token deleted, new token issued (rotation)
- Reuse detection: if a rotated token is presented again, ALL tokens for that user are deleted and a warning is logged
POST /api/auth/logoutdeletes all refresh tokens for the user
Social Auth
In addition to email/password, login via external providers is supported:
| Provider | Path | Description |
|---|---|---|
| Yandex ID | /api/auth/social/yandex | Login via Yandex |
| VK ID | /api/auth/social/vk | Login via VK |
Flow: frontend opens a popup window to the provider URL → after successful auth, the provider redirects to callback with tokens in URL hash → popup sends tokens to parent window via postMessage → parent window closes popup and stores tokens.
Token Payload
{
"sub": "uuid",
"email": "user@example.com",
"jti": "random-uuid",
"iat": 1234567890,
"exp": 1234568790
}Role-Based Access Control (RBAC)
The RBAC system provides granular permissions at multiple levels.
Permission Scopes
| Scope | Level | Example Controller |
|---|---|---|
| GLOBAL | System-wide | Admin panel, user management |
| ORGANIZATION | Per organization | Org settings, member management |
| TEAM | Per team | Team settings, member management |
| PROJECT | Per project | Project settings, task management |
| TASK | Per task | Task CRUD (controlled by project scope) |
| CHAT | Chat level | Conversation management |
Available Permissions (30+)
Organization (org:): create, view, edit, delete, members.view, members.invite, members.remove, members.roles, settings.view, settings.edit
Team (team:): create, view, edit, delete, members.view, members.add, members.remove, members.roles, settings.view, settings.edit
Project (project:): create, view, edit, delete, members.view, members.add, members.remove, task.create, task.view, task.edit, task.delete, task.assign, settings.view, settings.edit
Built-in Roles
| Role | Level | Description |
|---|---|---|
| OWNER | Org/Team/Project | Full control, can delete |
| ADMIN | Org/Team/Project | User and settings management |
| MANAGER | Org/Team/Project | Operational management |
| MEMBER | Org/Team/Project | Basic access |
Custom roles can be created with any combination of permissions.
Permission Resolution
Permissions are computed from all user roles:
effective_permissions = union(
global_roles.permissions,
org_roles.permissions,
team_roles.permissions,
project_roles.permissions
)Permission Checks in Code
Backend (NestJS):
@Permissions(['task.create', 'task.edit'])
@Controller('/projects/:projectId/tasks')Frontend (Pinia store):
const auth = useAuthStore()
// auth.hasPermission — computed, auto-unwrapped in templates
if (auth.hasPermission('task.create')) { ... }User Groups
User groups allow bulk role assignment. Create a group with a set of permissions, then add users to the group. All group members inherit the group's permissions.