943 lines
34 KiB
Markdown
943 lines
34 KiB
Markdown
# Backend Auth Module Documentation
|
|
|
|
## Overview
|
|
|
|
The Auth module provides comprehensive authentication and authorization for the application. It supports local email/password authentication, OAuth 2.0 (Google, Microsoft), JWT-based session management, email verification, and password reset flows.
|
|
|
|
**Files:**
|
|
| File | Purpose |
|
|
|------|---------|
|
|
| `src/auth/auth.ts` | Passport.js strategy configurations (JWT, Google, Microsoft) |
|
|
| `src/services/auth.ts` | Auth business logic (signin, password reset/update, email verification) |
|
|
| `src/routes/auth.ts` | REST API endpoints for authentication |
|
|
| `src/helpers.ts` | JWT signing utility (`jwtSign`) |
|
|
| `src/db/api/users.js` | User database operations (tokens, password updates) |
|
|
| `src/middlewares/rateLimiter.js` | Auth-specific rate limiters |
|
|
|
|
---
|
|
|
|
## Architecture Diagram
|
|
|
|
```
|
|
┌──────────────────────────────────────────────────────────────────────┐
|
|
│ Frontend/Client │
|
|
│ ┌────────────────┐ ┌────────────────┐ ┌────────────────────────┐ │
|
|
│ │ Login Form │ │ Signup Form │ │ OAuth Buttons │ │
|
|
│ │ (email/pass) │ │ (email/pass) │ │ (Google/Microsoft) │ │
|
|
│ └───────┬────────┘ └───────┬────────┘ └───────────┬────────────┘ │
|
|
└──────────┼───────────────────┼───────────────────────┼───────────────┘
|
|
│ │ │
|
|
▼ ▼ ▼
|
|
┌──────────────────────────────────────────────────────────────────────┐
|
|
│ Routes Layer (routes/auth.ts) │
|
|
│ │
|
|
│ ┌──────────────────────────────────────────────────────────────┐ │
|
|
│ │ Rate Limiters: │ │
|
|
│ │ • authLimiter (10 req/15min) → /signin/local │ │
|
|
│ │ • passwordResetLimiter (5 req/hour) → /send-password-reset │ │
|
|
│ └──────────────────────────────────────────────────────────────┘ │
|
|
│ │
|
|
│ ┌───────────────┐ ┌───────────────┐ ┌───────────────────────┐ │
|
|
│ │ POST /signin │ │ PUT /reset │ │ GET /signin/google │ │
|
|
│ │ /local │ │ │ │ GET /signin/microsoft │ │
|
|
│ └───────┬───────┘ └───────┬───────┘ └───────────┬───────────┘ │
|
|
│ │ │ │ │
|
|
└──────────┼──────────────────┼──────────────────────┼─────────────────┘
|
|
│ │ │
|
|
▼ ▼ ▼
|
|
┌──────────────────────────────────────────────────────────────────────┐
|
|
│ Service Layer (services/auth.ts) │
|
|
│ ┌────────────────────────────────────────────────────────────────┐ │
|
|
│ │ Auth Class (Static Methods) │ │
|
|
│ │ • signup(email, password, options, host) │ │
|
|
│ │ • signin(email, password) │ │
|
|
│ │ • verifyEmail(token) │ │
|
|
│ │ • passwordUpdate(currentPassword, newPassword, options) │ │
|
|
│ │ • passwordReset(token, password) │ │
|
|
│ │ • sendEmailAddressVerificationEmail(email, host) │ │
|
|
│ │ • sendPasswordResetEmail(email, type, host) │ │
|
|
│ │ • updateProfile(data, currentUser) │ │
|
|
│ └────────────────────────────────────────────────────────────────┘ │
|
|
└──────────────────────────────────────────────────────────────────────┘
|
|
│ │ │
|
|
▼ ▼ ▼
|
|
┌──────────────────────────────────────────────────────────────────────┐
|
|
│ Passport Layer (auth/auth.ts) │
|
|
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────────┐ │
|
|
│ │ JWT Strategy │ │ Google Strategy │ │ Microsoft Strategy │ │
|
|
│ │ (API Auth) │ │ (OAuth 2.0) │ │ (OAuth 2.0) │ │
|
|
│ └─────────────────┘ └─────────────────┘ └─────────────────────┘ │
|
|
│ │ │ │
|
|
│ └──────────┬───────────┘ │
|
|
│ │ │
|
|
│ ┌──────────▼──────────┐ │
|
|
│ │ socialStrategy() │ │
|
|
│ │ • findOrCreate │ │
|
|
│ │ • Generate JWT │ │
|
|
│ └─────────────────────┘ │
|
|
└──────────────────────────────────────────────────────────────────────┘
|
|
│
|
|
▼
|
|
┌──────────────────────────────────────────────────────────────────────┐
|
|
│ Database Layer (db/api/users.js) │
|
|
│ ┌────────────────────────────────────────────────────────────────┐ │
|
|
│ │ UsersDBApi (Static Methods) │ │
|
|
│ │ • findBy({ email }) │ │
|
|
│ │ • createFromAuth(data) │ │
|
|
│ │ • updatePassword(id, password) │ │
|
|
│ │ • generateEmailVerificationToken(email) │ │
|
|
│ │ • generatePasswordResetToken(email) │ │
|
|
│ │ • findByEmailVerificationToken(token) │ │
|
|
│ │ • findByPasswordResetToken(token) │ │
|
|
│ │ • markEmailVerified(id) │ │
|
|
│ └────────────────────────────────────────────────────────────────┘ │
|
|
└──────────────────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
---
|
|
|
|
## Authentication Strategies
|
|
|
|
### 1. JWT Strategy (Primary)
|
|
|
|
Used for API authentication on all protected routes.
|
|
|
|
**Configuration (auth/auth.ts):**
|
|
```javascript
|
|
passport.use(
|
|
new JWTstrategy({
|
|
passReqToCallback: true,
|
|
secretOrKey: config.secret_key,
|
|
jwtFromRequest: ExtractJWT.fromAuthHeaderAsBearerToken(),
|
|
}, async (req, token, done) => {
|
|
const user = await UsersDBApi.findBy({ email: token.user.email });
|
|
|
|
if (user && user.disabled) {
|
|
return done(new Error(`User '${user.email}' is disabled`));
|
|
}
|
|
|
|
req.currentUser = user;
|
|
return done(null, user);
|
|
})
|
|
);
|
|
```
|
|
|
|
**Token Structure:**
|
|
```javascript
|
|
{
|
|
user: {
|
|
id: "uuid",
|
|
email: "user@example.com"
|
|
},
|
|
iat: 1234567890, // Issued at
|
|
exp: 1234589490 // Expires in 6 hours
|
|
}
|
|
```
|
|
|
|
**Usage:**
|
|
```javascript
|
|
// Protect route with JWT
|
|
router.get('/me', passport.authenticate('jwt', { session: false }), handler);
|
|
|
|
// Access user in handler
|
|
const currentUser = req.currentUser;
|
|
```
|
|
|
|
### 2. Google OAuth Strategy
|
|
|
|
**Configuration (auth/auth.ts):**
|
|
```javascript
|
|
passport.use(
|
|
new GoogleStrategy({
|
|
clientID: config.google.clientId,
|
|
clientSecret: config.google.clientSecret,
|
|
callbackURL: config.apiUrl + '/auth/signin/google/callback',
|
|
passReqToCallback: true,
|
|
}, (request, accessToken, refreshToken, profile, done) => {
|
|
socialStrategy(profile.email, profile, providers.GOOGLE, done);
|
|
})
|
|
);
|
|
```
|
|
|
|
**Environment Variables:**
|
|
| Variable | Description |
|
|
|----------|-------------|
|
|
| `GOOGLE_CLIENT_ID` | Google OAuth client ID |
|
|
| `GOOGLE_CLIENT_SECRET` | Google OAuth client secret |
|
|
|
|
**OAuth Scopes:** `profile`, `email`
|
|
|
|
### 3. Microsoft OAuth Strategy
|
|
|
|
**Configuration (auth/auth.ts):**
|
|
```javascript
|
|
passport.use(
|
|
new MicrosoftStrategy({
|
|
clientID: config.microsoft.clientId,
|
|
clientSecret: config.microsoft.clientSecret,
|
|
callbackURL: config.apiUrl + '/auth/signin/microsoft/callback',
|
|
passReqToCallback: true,
|
|
}, (request, accessToken, refreshToken, profile, done) => {
|
|
const email = profile._json.mail || profile._json.userPrincipalName;
|
|
socialStrategy(email, profile, providers.MICROSOFT, done);
|
|
})
|
|
);
|
|
```
|
|
|
|
**Environment Variables:**
|
|
| Variable | Description |
|
|
|----------|-------------|
|
|
| `MS_CLIENT_ID` | Microsoft OAuth client ID |
|
|
| `MS_CLIENT_SECRET` | Microsoft OAuth client secret |
|
|
|
|
**OAuth Scopes:** `https://graph.microsoft.com/user.read`, `openid`
|
|
|
|
### Social Strategy Helper
|
|
|
|
Common logic for OAuth providers:
|
|
|
|
```javascript
|
|
function socialStrategy(email, profile, provider, done) {
|
|
db.users.findOrCreate({ where: { email, provider } }).then(([user]) => {
|
|
const body = {
|
|
id: user.id,
|
|
email: user.email,
|
|
name: profile.displayName,
|
|
};
|
|
const token = helpers.jwtSign({ user: body });
|
|
return done(null, { token });
|
|
});
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## File Details
|
|
|
|
### 1. services/auth.ts
|
|
|
|
Core authentication business logic.
|
|
|
|
#### Class: Auth
|
|
|
|
```typescript
|
|
class Auth {
|
|
static async signin(email, password)
|
|
static async verifyEmail(token, options)
|
|
static async passwordUpdate(currentPassword, newPassword, options)
|
|
static async passwordReset(token, password, options)
|
|
static async sendEmailAddressVerificationEmail(email, host)
|
|
static async sendPasswordResetEmail(email, type, host)
|
|
static async updateProfile(data, currentUser)
|
|
}
|
|
```
|
|
|
|
#### Method: signup(email, password, options, host)
|
|
|
|
Registers a new user or updates password for existing unverified user.
|
|
|
|
**Flow:**
|
|
```
|
|
1. Check if user exists by email
|
|
├── User exists with authenticationUid → Error: emailAlreadyInUse
|
|
├── User exists but disabled → Error: userDisabled
|
|
└── User exists without authenticationUid → Update password
|
|
2. If new user:
|
|
├── Hash password (bcrypt, 12 rounds)
|
|
├── Create user via UsersDBApi.createFromAuth()
|
|
└── Assign default "User" role
|
|
3. Send verification email (if EmailSender configured)
|
|
4. Return signed JWT token
|
|
```
|
|
|
|
**Returns:** JWT token string
|
|
|
|
#### Method: signin(email, password)
|
|
|
|
Authenticates user with email and password.
|
|
|
|
**Flow:**
|
|
```
|
|
1. Find user by email
|
|
└── Not found → Error: userNotFound
|
|
2. Check if disabled
|
|
└── Disabled → Error: userDisabled
|
|
3. Verify password exists
|
|
└── No password → Error: wrongPassword
|
|
4. Check email verification
|
|
└── Not verified (and email configured) → Error: userNotVerified
|
|
5. Compare password with bcrypt
|
|
└── Mismatch → Error: wrongPassword
|
|
6. Return signed JWT token
|
|
```
|
|
|
|
**Returns:** JWT token string
|
|
|
|
#### Method: verifyEmail(token, options)
|
|
|
|
Verifies user email address using token.
|
|
|
|
**Flow:**
|
|
```
|
|
1. Find user by email verification token
|
|
└── Not found or expired → Error: invalidToken
|
|
2. Mark email as verified
|
|
3. Return true
|
|
```
|
|
|
|
#### Method: passwordUpdate(currentPassword, newPassword, options)
|
|
|
|
Updates password for authenticated user.
|
|
|
|
**Flow:**
|
|
```
|
|
1. Verify currentUser exists
|
|
└── Not authenticated → ForbiddenError
|
|
2. Verify current password matches
|
|
└── Mismatch → Error: wrongPassword
|
|
3. Verify new password is different
|
|
└── Same → Error: samePassword
|
|
4. Hash new password and update
|
|
```
|
|
|
|
#### Method: passwordReset(token, password, options)
|
|
|
|
Resets password using reset token.
|
|
|
|
**Flow:**
|
|
```
|
|
1. Find user by password reset token
|
|
└── Not found or expired → Error: invalidToken
|
|
2. Hash new password
|
|
3. Update user password
|
|
```
|
|
|
|
---
|
|
|
|
### 2. routes/auth.ts (327 lines)
|
|
|
|
REST API endpoints for authentication.
|
|
|
|
#### Endpoints Overview
|
|
|
|
| Method | Path | Auth | Rate Limit | Description |
|
|
|--------|------|------|------------|-------------|
|
|
| POST | `/signin/local` | No | authLimiter | Login with email/password |
|
|
| GET | `/me` | JWT | - | Get current user |
|
|
| PUT | `/password-reset` | No | - | Reset password with token |
|
|
| PUT | `/password-update` | JWT | - | Change password |
|
|
| PUT | `/profile` | JWT | - | Update user profile |
|
|
| PUT | `/verify-email` | No | - | Verify email with token |
|
|
| POST | `/send-email-address-verification-email` | JWT | - | Resend verification email |
|
|
| POST | `/send-password-reset-email` | No | passwordResetLimiter | Send password reset email |
|
|
| GET | `/email-configured` | No | - | Check if email is configured |
|
|
| GET | `/signin/google` | No | - | Initiate Google OAuth |
|
|
| GET | `/signin/google/callback` | No | - | Google OAuth callback |
|
|
| GET | `/signin/microsoft` | No | - | Initiate Microsoft OAuth |
|
|
| GET | `/signin/microsoft/callback` | No | - | Microsoft OAuth callback |
|
|
|
|
#### POST /api/auth/signin/local
|
|
|
|
Login with email and password.
|
|
|
|
**Request:**
|
|
```json
|
|
{
|
|
"email": "user@example.com",
|
|
"password": "securepassword123"
|
|
}
|
|
```
|
|
|
|
**Response (200):**
|
|
```json
|
|
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
|
|
```
|
|
|
|
**Errors:**
|
|
| Code | Message | Cause |
|
|
|------|---------|-------|
|
|
| 400 | `auth.userNotFound` | User doesn't exist |
|
|
| 400 | `auth.userDisabled` | User account is disabled |
|
|
| 400 | `auth.wrongPassword` | Invalid password |
|
|
| 400 | `auth.userNotVerified` | Email not verified |
|
|
| 429 | Too Many Requests | Rate limit exceeded |
|
|
|
|
#### Self-Registration
|
|
|
|
Self-registration is disabled. `POST /api/auth/signup` is not registered.
|
|
New users are created through the authenticated Users flow and receive an
|
|
invitation/setup link.
|
|
|
|
#### GET /api/auth/me
|
|
|
|
Get current authenticated user.
|
|
|
|
**Headers:**
|
|
```
|
|
Authorization: Bearer <JWT_TOKEN>
|
|
```
|
|
|
|
**Response (200):**
|
|
```json
|
|
{
|
|
"id": "uuid",
|
|
"email": "user@example.com",
|
|
"firstName": "John",
|
|
"lastName": "Doe",
|
|
"emailVerified": true,
|
|
"disabled": false,
|
|
"app_role": {
|
|
"id": "uuid",
|
|
"name": "Administrator",
|
|
"permissions": [...]
|
|
},
|
|
"custom_permissions": [],
|
|
"avatar": [...],
|
|
"createdAt": "2024-01-01T00:00:00.000Z"
|
|
}
|
|
```
|
|
|
|
**Note:** Password field is omitted from response.
|
|
|
|
#### PUT /api/auth/password-reset
|
|
|
|
Reset password using token from email.
|
|
|
|
**Request:**
|
|
```json
|
|
{
|
|
"token": "abc123...",
|
|
"password": "newSecurePassword456"
|
|
}
|
|
```
|
|
|
|
**Response (200):**
|
|
```json
|
|
{ "success": true }
|
|
```
|
|
|
|
#### PUT /api/auth/password-update
|
|
|
|
Change password for authenticated user.
|
|
|
|
**Headers:**
|
|
```
|
|
Authorization: Bearer <JWT_TOKEN>
|
|
```
|
|
|
|
**Request:**
|
|
```json
|
|
{
|
|
"currentPassword": "oldPassword123",
|
|
"newPassword": "newPassword456"
|
|
}
|
|
```
|
|
|
|
**Errors:**
|
|
| Code | Message | Cause |
|
|
|------|---------|-------|
|
|
| 400 | `auth.wrongPassword` | Current password incorrect |
|
|
| 400 | `auth.passwordUpdate.samePassword` | New password same as old |
|
|
| 403 | Forbidden | Not authenticated |
|
|
|
|
#### PUT /api/auth/profile
|
|
|
|
Update user profile.
|
|
|
|
**Headers:**
|
|
```
|
|
Authorization: Bearer <JWT_TOKEN>
|
|
```
|
|
|
|
**Request:**
|
|
```json
|
|
{
|
|
"profile": {
|
|
"firstName": "John",
|
|
"lastName": "Smith",
|
|
"phoneNumber": "+1234567890"
|
|
}
|
|
}
|
|
```
|
|
|
|
#### OAuth Endpoints
|
|
|
|
**GET /api/auth/signin/google**
|
|
- Redirects to Google OAuth consent screen
|
|
- Query param: `app` (passed as state)
|
|
|
|
**GET /api/auth/signin/google/callback**
|
|
- Handles Google OAuth callback
|
|
- Redirects to: `{uiUrl}/login?token={jwt}`
|
|
|
|
**GET /api/auth/signin/microsoft**
|
|
- Redirects to Microsoft OAuth consent screen
|
|
- Query param: `app` (passed as state)
|
|
|
|
**GET /api/auth/signin/microsoft/callback**
|
|
- Handles Microsoft OAuth callback
|
|
- Redirects to: `{uiUrl}/login?token={jwt}`
|
|
|
|
---
|
|
|
|
### 3. helpers.js (32 lines)
|
|
|
|
JWT and utility functions.
|
|
|
|
#### Method: jwtSign(data)
|
|
|
|
Signs JWT token with application secret.
|
|
|
|
```javascript
|
|
static jwtSign(data) {
|
|
return jwt.sign(data, config.secret_key, { expiresIn: '6h' });
|
|
}
|
|
```
|
|
|
|
**Configuration:**
|
|
| Setting | Value | Description |
|
|
|---------|-------|-------------|
|
|
| Secret Key | `config.secret_key` | From `SECRET_KEY` env var |
|
|
| Expiration | `6h` | Token valid for 6 hours |
|
|
| Algorithm | `HS256` | Default HMAC SHA-256 |
|
|
|
|
---
|
|
|
|
### 4. db/api/users.js (Authentication Methods)
|
|
|
|
User database operations related to authentication.
|
|
|
|
#### Method: createFromAuth(data)
|
|
|
|
Creates new user during signup.
|
|
|
|
```javascript
|
|
static async createFromAuth(data, options) {
|
|
const users = await db.users.create({
|
|
email: data.email,
|
|
firstName: data.firstName,
|
|
authenticationUid: data.authenticationUid,
|
|
password: data.password,
|
|
}, { transaction });
|
|
|
|
// Assign default "User" role
|
|
const app_role = await db.roles.findOne({
|
|
where: { name: config.roles?.user || 'User' },
|
|
});
|
|
await users.setApp_role(app_role?.id);
|
|
|
|
return users;
|
|
}
|
|
```
|
|
|
|
#### Method: generateEmailVerificationToken(email)
|
|
|
|
Generates secure token for email verification.
|
|
|
|
```javascript
|
|
static async generateEmailVerificationToken(email, options) {
|
|
const token = crypto.randomBytes(20).toString('hex');
|
|
const tokenExpiresAt = Date.now() + (24 * 60 * 60 * 1000); // 24 hours
|
|
|
|
await users.update({
|
|
emailVerificationToken: token,
|
|
emailVerificationTokenExpiresAt: tokenExpiresAt,
|
|
});
|
|
|
|
return token;
|
|
}
|
|
```
|
|
|
|
**Token Properties:**
|
|
| Property | Value |
|
|
|----------|-------|
|
|
| Length | 40 hex characters |
|
|
| Expiry | 24 hours |
|
|
| Storage | `emailVerificationToken` column |
|
|
|
|
#### Method: generatePasswordResetToken(email)
|
|
|
|
Generates secure token for password reset.
|
|
|
|
Same implementation as `generateEmailVerificationToken` but stores in:
|
|
- `passwordResetToken`
|
|
- `passwordResetTokenExpiresAt`
|
|
|
|
#### Method: findByEmailVerificationToken(token)
|
|
|
|
Finds user by valid (non-expired) verification token.
|
|
|
|
```javascript
|
|
static async findByEmailVerificationToken(token, options) {
|
|
return db.users.findOne({
|
|
where: {
|
|
emailVerificationToken: token,
|
|
emailVerificationTokenExpiresAt: {
|
|
[Op.gt]: Date.now(),
|
|
},
|
|
},
|
|
});
|
|
}
|
|
```
|
|
|
|
#### Method: markEmailVerified(id)
|
|
|
|
Marks user email as verified.
|
|
|
|
```javascript
|
|
static async markEmailVerified(id, options) {
|
|
const users = await db.users.findByPk(id);
|
|
await users.update({ emailVerified: true });
|
|
return true;
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Rate Limiting
|
|
|
|
Authentication endpoints have dedicated rate limiters defined in `middlewares/rateLimiter.js`.
|
|
|
|
### Auth Limiter (Login)
|
|
|
|
```javascript
|
|
const authLimiter = createRateLimiter({
|
|
keyPrefix: 'auth',
|
|
windowMs: 15 * 60 * 1000, // 15 minutes
|
|
max: 10,
|
|
message: 'Too many authentication attempts. Please try again later.',
|
|
skipFailedRequests: false, // Count ALL attempts
|
|
});
|
|
```
|
|
|
|
| Setting | Value |
|
|
|---------|-------|
|
|
| Window | 15 minutes |
|
|
| Max Requests | 10 |
|
|
| Applied To | `/signin/local` |
|
|
|
|
### Signup Limiter
|
|
|
|
Self-registration is disabled, so no signup limiter is registered.
|
|
|
|
### Password Reset Limiter
|
|
|
|
```javascript
|
|
const passwordResetLimiter = createRateLimiter({
|
|
keyPrefix: 'password-reset',
|
|
windowMs: 60 * 60 * 1000, // 1 hour
|
|
max: 5,
|
|
message: 'Too many password reset requests. Please try again later.',
|
|
});
|
|
```
|
|
|
|
| Setting | Value |
|
|
|---------|-------|
|
|
| Window | 1 hour |
|
|
| Max Requests | 5 |
|
|
| Applied To | `/send-password-reset-email` |
|
|
|
|
### Rate Limit Response
|
|
|
|
```json
|
|
{
|
|
"error": "Too Many Requests",
|
|
"message": "Too many authentication attempts. Please try again later.",
|
|
"retryAfter": 300
|
|
}
|
|
```
|
|
|
|
**Headers:**
|
|
```
|
|
X-RateLimit-Limit: 10
|
|
X-RateLimit-Remaining: 0
|
|
X-RateLimit-Reset: 2024-01-01T00:15:00.000Z
|
|
Retry-After: 300
|
|
```
|
|
|
|
---
|
|
|
|
## Password Security
|
|
|
|
### Hashing Configuration
|
|
|
|
```javascript
|
|
// config.ts
|
|
bcrypt: {
|
|
saltRounds: 12,
|
|
}
|
|
```
|
|
|
|
| Setting | Value | Security Impact |
|
|
|---------|-------|-----------------|
|
|
| Algorithm | bcrypt | Industry standard |
|
|
| Salt Rounds | 12 | ~200ms hash time |
|
|
| Salt | Auto-generated | Per-password unique |
|
|
|
|
### Password Validation
|
|
|
|
Passwords are:
|
|
1. Hashed before storage (never stored in plain text)
|
|
2. Compared using `bcrypt.compare()` (timing-attack safe)
|
|
3. Required for local authentication
|
|
|
|
---
|
|
|
|
## Email Integration
|
|
|
|
Email functionality is conditional based on configuration.
|
|
|
|
### Email Configuration Check
|
|
|
|
```javascript
|
|
if (EmailSender.isConfigured) {
|
|
await this.sendEmailAddressVerificationEmail(user.email, host);
|
|
}
|
|
```
|
|
|
|
When email is NOT configured:
|
|
- Signup succeeds without verification email
|
|
- Users are auto-verified on signin
|
|
- Password reset emails not sent
|
|
|
|
### Verification Email
|
|
|
|
**Email Class:** `EmailAddressVerificationEmail`
|
|
|
|
**Link Format:**
|
|
```
|
|
{host}/verify-email?token={token}
|
|
```
|
|
|
|
### Password Reset Email
|
|
|
|
**Email Classes:**
|
|
- `PasswordResetEmail` - Standard reset
|
|
- `InvitationEmail` - New user invitation
|
|
|
|
**Link Format:**
|
|
```
|
|
{host}/password-reset?token={token}
|
|
```
|
|
|
|
---
|
|
|
|
## Configuration Reference
|
|
|
|
### Environment Variables
|
|
|
|
| Variable | Required | Default | Description |
|
|
|----------|----------|---------|-------------|
|
|
| `SECRET_KEY` | Yes | `88dbeaf8-e906-405e-9e41-c3baadeda5c6` | JWT signing secret |
|
|
| `GOOGLE_CLIENT_ID` | No | - | Google OAuth client ID |
|
|
| `GOOGLE_CLIENT_SECRET` | No | - | Google OAuth client secret |
|
|
| `MS_CLIENT_ID` | No | - | Microsoft OAuth client ID |
|
|
| `MS_CLIENT_SECRET` | No | - | Microsoft OAuth client secret |
|
|
| `ADMIN_EMAIL` | No | `admin@flatlogic.com` | Default admin email |
|
|
| `ADMIN_PASS` | No | `88dbeaf8` | Default admin password |
|
|
| `USER_PASS` | No | `c3baadeda5c6` | Default user password |
|
|
|
|
### config.ts Settings
|
|
|
|
```javascript
|
|
{
|
|
bcrypt: { saltRounds: 12 },
|
|
secret_key: env.SECRET_KEY,
|
|
providers: {
|
|
LOCAL: 'local',
|
|
GOOGLE: 'google',
|
|
MICROSOFT: 'microsoft',
|
|
},
|
|
google: {
|
|
clientId: env.GOOGLE_CLIENT_ID,
|
|
clientSecret: env.GOOGLE_CLIENT_SECRET,
|
|
},
|
|
microsoft: {
|
|
clientId: env.MS_CLIENT_ID,
|
|
clientSecret: env.MS_CLIENT_SECRET,
|
|
},
|
|
roles: {
|
|
admin: 'Administrator',
|
|
user: 'Analytics Viewer',
|
|
},
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Authentication Flows
|
|
|
|
### Local Login Flow
|
|
|
|
```
|
|
┌────────┐ ┌─────────┐ ┌─────────────┐ ┌──────────┐
|
|
│ Client │────▶│ Router │────▶│ AuthService │────▶│ UsersDB │
|
|
└────────┘ └─────────┘ └─────────────┘ └──────────┘
|
|
│ │ │ │
|
|
│ POST /signin │ │ │
|
|
│ {email,pass} │ │ │
|
|
│──────────────▶│ │ │
|
|
│ │ signin() │ │
|
|
│ │───────────────▶│ │
|
|
│ │ │ findBy({email}) │
|
|
│ │ │──────────────────▶│
|
|
│ │ │◀──────────────────│
|
|
│ │ │ │
|
|
│ │ │ bcrypt.compare() │
|
|
│ │ │ │
|
|
│ │ │ jwtSign() │
|
|
│ │◀───────────────│ │
|
|
│◀──────────────│ │ │
|
|
│ JWT Token │ │ │
|
|
```
|
|
|
|
### OAuth Login Flow
|
|
|
|
```
|
|
┌────────┐ ┌─────────┐ ┌──────────┐ ┌──────────┐
|
|
│ Client │ │ Backend │ │ Provider │ │ UsersDB │
|
|
└────────┘ └─────────┘ └──────────┘ └──────────┘
|
|
│ │ │ │
|
|
│ GET /signin/ │ │ │
|
|
│ google │ │ │
|
|
│──────────────▶│ │ │
|
|
│ │ Redirect to │ │
|
|
│◀──────────────│ consent screen │ │
|
|
│──────────────────────────────▶│ │
|
|
│ │ │ │
|
|
│ │ User consents │ │
|
|
│◀──────────────────────────────│ │
|
|
│ │ │ │
|
|
│ GET /callback │ │ │
|
|
│ ?code=... │ │ │
|
|
│──────────────▶│ │ │
|
|
│ │ Exchange code │ │
|
|
│ │───────────────▶│ │
|
|
│ │ Profile data │ │
|
|
│ │◀───────────────│ │
|
|
│ │ │ │
|
|
│ │ findOrCreate() │ │
|
|
│ │───────────────────────────────▶│
|
|
│ │◀───────────────────────────────│
|
|
│ │ jwtSign() │ │
|
|
│ │ │ │
|
|
│ Redirect to │ │ │
|
|
│ /login?token= │ │ │
|
|
│◀──────────────│ │ │
|
|
```
|
|
|
|
### Email Verification Flow
|
|
|
|
```
|
|
┌────────┐ ┌─────────┐ ┌─────────────┐ ┌───────┐
|
|
│ Client │ │ Router │ │ AuthService │ │ Email │
|
|
└────────┘ └─────────┘ └─────────────┘ └───────┘
|
|
│ │ │ │
|
|
│ POST /signup │ │ │
|
|
│──────────────▶│ │ │
|
|
│ │ signup() │ │
|
|
│ │───────────────▶│ │
|
|
│ │ │ generateToken()│
|
|
│ │ │ sendEmail() │
|
|
│ │ │───────────────▶│
|
|
│ │◀───────────────│ │
|
|
│◀──────────────│ │ │
|
|
│ JWT Token │ │ │
|
|
│ │ │ │
|
|
│ User clicks email link │ │
|
|
│ │ │ │
|
|
│ PUT /verify- │ │ │
|
|
│ email │ │ │
|
|
│ {token} │ │ │
|
|
│──────────────▶│ │ │
|
|
│ │ verifyEmail() │ │
|
|
│ │───────────────▶│ │
|
|
│ │ │ markVerified() │
|
|
│ │◀───────────────│ │
|
|
│◀──────────────│ │ │
|
|
│ Success │ │ │
|
|
```
|
|
|
|
---
|
|
|
|
## Error Codes
|
|
|
|
| Error Key | HTTP Status | Description |
|
|
|-----------|-------------|-------------|
|
|
| `auth.userNotFound` | 400 | User with email doesn't exist |
|
|
| `auth.userDisabled` | 400 | User account is disabled |
|
|
| `auth.wrongPassword` | 400 | Password doesn't match |
|
|
| `auth.userNotVerified` | 400 | Email not verified |
|
|
| `auth.emailAlreadyInUse` | 400 | Email already registered |
|
|
| `auth.passwordUpdate.samePassword` | 400 | New password same as current |
|
|
| `auth.passwordReset.error` | 400 | Token generation failed |
|
|
| `auth.passwordReset.invalidToken` | 400 | Invalid or expired reset token |
|
|
| `auth.emailAddressVerificationEmail.error` | 400 | Verification email failed |
|
|
| `auth.emailAddressVerificationEmail.invalidToken` | 400 | Invalid verification token |
|
|
|
|
---
|
|
|
|
## Security Considerations
|
|
|
|
1. **Password Storage:** bcrypt with 12 salt rounds
|
|
2. **JWT Expiration:** 6 hours (balances security and UX)
|
|
3. **Token Generation:** crypto.randomBytes(20) - 160-bit entropy
|
|
4. **Token Expiry:** 24 hours for email/password reset tokens
|
|
5. **Rate Limiting:** Prevents brute force attacks
|
|
6. **Disabled User Check:** Checked on both JWT validation and login
|
|
7. **Session-less:** No server-side session storage (stateless JWT)
|
|
8. **HTTPS Required:** OAuth callbacks require HTTPS in production
|
|
|
|
---
|
|
|
|
## Testing
|
|
|
|
### Test Local Login
|
|
|
|
```bash
|
|
curl -X POST http://localhost:3000/api/auth/signin/local \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"email": "admin@flatlogic.com", "password": "password"}'
|
|
```
|
|
|
|
### Test Get Current User
|
|
|
|
```bash
|
|
curl http://localhost:3000/api/auth/me \
|
|
-H "Authorization: Bearer <JWT_TOKEN>"
|
|
```
|
|
|
|
### User Creation
|
|
|
|
Use the authenticated Users API/UI to create invited users.
|
|
|
|
## Dependencies
|
|
|
|
| Package | Version | Purpose |
|
|
|---------|---------|---------|
|
|
| `passport` | ^0.6.0 | Authentication middleware |
|
|
| `passport-jwt` | ^4.0.0 | JWT strategy for Passport |
|
|
| `passport-google-oauth2` | ^0.2.0 | Google OAuth strategy |
|
|
| `passport-microsoft` | ^2.0.0 | Microsoft OAuth strategy |
|
|
| `@types/passport-jwt` | ^4.0.1 | Maintained TypeScript definitions for JWT Passport strategy |
|
|
| `@types/passport-google-oauth2` | ^0.1.10 | Maintained TypeScript definitions for Google OAuth Passport strategy |
|
|
| `@types/passport-microsoft` | ^2.1.1 | Maintained TypeScript definitions for Microsoft Passport strategy |
|
|
| `jsonwebtoken` | ^9.0.0 | JWT sign/verify |
|
|
| `bcrypt` | ^5.1.0 | Password hashing |
|
|
| `crypto` | built-in | Token generation |
|
|
|
|
---
|
|
|
|
## Summary
|
|
|
|
The Auth module provides:
|
|
|
|
1. **JWT Authentication** - Stateless API authentication with 6-hour tokens
|
|
2. **Local Login** - Email/password authentication with bcrypt
|
|
3. **OAuth 2.0** - Google and Microsoft social login
|
|
4. **Email Verification** - Token-based email confirmation
|
|
5. **Password Reset** - Secure token-based password recovery
|
|
6. **Rate Limiting** - Protection against brute force attacks
|
|
7. **Profile Management** - User profile updates
|
|
8. **Role Assignment** - Default role on signup
|