mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 18:01:09 +02:00
make auth working
This commit is contained in:
parent
28d167a978
commit
7a1f1e9aef
10 changed files with 302 additions and 52 deletions
|
|
@ -12,8 +12,9 @@ REDIS_PORT=6379
|
||||||
REDIS_PASSWORD=
|
REDIS_PASSWORD=
|
||||||
|
|
||||||
# JWT Configuration
|
# JWT Configuration
|
||||||
JWT_PUBLIC_KEY="-----BEGIN PUBLIC KEY-----\nYOUR_PUBLIC_KEY_HERE\n-----END PUBLIC KEY-----"
|
JWT_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDGRsOXROB4lprw\n9oXaOIt+cwHe3UxBOoiWiUXcpFuXwb+kBWn/LyjeCIOXtefOwE0S10JEodK+6foe\naqGHanq86qAmmkb4a8sjj5LAxXkHL35sJo8HaYcx5NkJQLxQSRHpTfdfxsKsKwxa\n4R4uqrvToqdo6tl/VMsGDPS8L7KzaiKaSdGugvlVtXWgV1soeXSUPyPwpyAXQg7h\nY4CkTSkJAplrs77RLdj8u6jbHKR3F7QkwiU1JocjhM1GP/suKiqXRu8omLFnu45C\ns09SNSRsOpNY5csrKA4PZ2LCks9VHH7HafFvB+BbRw4+Ssr6myOysAztqi3bZMRW\nLTakWpBbAgMBAAECggEAF5zi0IzaghHxhtkyYfrSRgSynX9+WYBRNu2ch8/SZqAj\neghOXMkZgAPEjtiSMDGqRsr4ReMoYtB2Qea8sOX8kwC1gj4Po1Mhtez0cwexclUf\nebLH3X/y9/1YiZJk5YImOMIuaoC/ELDvFOhIEhJcMbKREbIc+oiMcH6HgN0vViVh\nJptgHTnqnGHNARkEpf+xnxqJJxEgrEMz50b4fApKpoZsWXNnZ3Atc/i2ziGew5z4\npnGJxs9TWSukBZaQvl9iluBBvqmPkCOId+L7CmB44bNURpqQOm8gxEgLcdn06y5j\nIKee3Z4H6OTseFvSIYYqBqCyyyZWHICBZXUCDQKUbQKBgQDnFe+O+pQc5looLFiF\nxuYsfDtJqvoMgQ0BaVAo6wVpPe6w+1NA6ZxghcM0+8zyc70jZvdMXINhdsfWD5Gi\nJ/NEDI8EXJJKMfnFQ7F1Ad5NyTnnn/TsLda4GIGQznPRS6uxUP4ljFtxmU9G8Diz\nUQ47XsLjwzzbTedMTSYoQ46kdwKBgQDbp0dIq047o4A72/BBttKdZbgQmjFmqCXF\n8YRUquIDXh/CJ4OQwOIaOvk2398Rg53c3MsV+XCJaMmWYqnJ4BdITLsqeGKsczoS\nI0DMehDr++aOoX/f29r1c+7J/fV5jtAEUcwIEOR1vyAM+WdiWnnTvdpMPVUDsgaT\ntuH0E8WgPQKBgQCCINci87Z+Q7VXVAmRY7zwJhEY3eArNGzHc6+BKz+D0S1dmll6\nf1LhA9I2PuldSpGiovP1m08cjk/gGipPXyHdGxlaQmravyPA0urWUfQGZ59k8K1y\nZim4x4wGqEuN+4e2tT44lL5VzRhYgSPcznMuOaGTsrjNYiQy0mr/V3O25wKBgHvV\nryaVDaIp553XvXgO7ma2djNF+xv5KHKUWxqwzINBiX4YcOAnHlHTdbUuOcDSByoB\ngK1+16dgYGZccYTSxc2JFOw4usimndKj9WBSYT/p4G4BNuqqNKO1HKbceoxxq20E\nAJd7jpGjkxo9cb/Nammp22yoF0niEDsvG+xTSVOxAoGBAMfxHYCMdPc625upCbqG\nkPSJJGYREKGad80OtXilYXLvBPzV65q32k2YZGjaicPKRAzj72KO4nfIu9SY6bfO\nBvXCtIcvllZQuxyd3Cd8MirujJodKwThLTMd4bAYYMXGz1/W6R6pzunZs5KEpgEr\nczy9Gk9WNp0t8vfzyZZ9aago\n-----END PRIVATE KEY-----\n"
|
||||||
JWT_PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----\nYOUR_PRIVATE_KEY_HERE\n-----END RSA PRIVATE KEY-----"
|
|
||||||
|
JWT_PUBLIC_KEY="-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxkbDl0TgeJaa8PaF2jiL\nfnMB3t1MQTqIlolF3KRbl8G/pAVp/y8o3giDl7XnzsBNEtdCRKHSvun6Hmqhh2p6\nvOqgJppG+GvLI4+SwMV5By9+bCaPB2mHMeTZCUC8UEkR6U33X8bCrCsMWuEeLqq7\n06KnaOrZf1TLBgz0vC+ys2oimknRroL5VbV1oFdbKHl0lD8j8KcgF0IO4WOApE0p\nCQKZa7O+0S3Y/Luo2xykdxe0JMIlNSaHI4TNRj/7Lioql0bvKJixZ7uOQrNPUjUk\nbDqTWOXLKygOD2diwpLPVRx+x2nxbwfgW0cOPkrK+psjsrAM7aot22TEVi02pFqQ\nWwIDAQAB\n-----END PUBLIC KEY-----\n"
|
||||||
JWT_ACCESS_TOKEN_EXPIRY=15m
|
JWT_ACCESS_TOKEN_EXPIRY=15m
|
||||||
JWT_REFRESH_TOKEN_EXPIRY=7d
|
JWT_REFRESH_TOKEN_EXPIRY=7d
|
||||||
JWT_ISSUER=manacore
|
JWT_ISSUER=manacore
|
||||||
|
|
|
||||||
|
|
@ -6,18 +6,18 @@ RUN npm install -g pnpm@9.15.0
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
# Copy package files
|
# Copy package files for mana-core-auth only (standalone build)
|
||||||
COPY package.json pnpm-lock.yaml* pnpm-workspace.yaml* ./
|
COPY mana-core-auth/package.json ./
|
||||||
COPY mana-core-auth/package.json ./mana-core-auth/
|
|
||||||
|
|
||||||
# Install dependencies
|
# Install all dependencies (including devDependencies for build)
|
||||||
RUN pnpm install --frozen-lockfile
|
RUN pnpm install
|
||||||
|
|
||||||
# Copy source code
|
# Copy source code
|
||||||
COPY mana-core-auth ./mana-core-auth
|
COPY mana-core-auth/src ./src
|
||||||
|
COPY mana-core-auth/tsconfig*.json ./
|
||||||
|
COPY mana-core-auth/nest-cli.json ./
|
||||||
|
|
||||||
# Build the application
|
# Build the application
|
||||||
WORKDIR /app/mana-core-auth
|
|
||||||
RUN pnpm build
|
RUN pnpm build
|
||||||
|
|
||||||
# Production stage
|
# Production stage
|
||||||
|
|
@ -29,18 +29,19 @@ RUN npm install -g pnpm@9.15.0
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
# Copy package files
|
# Copy package files
|
||||||
COPY --from=builder /app/package.json /app/pnpm-lock.yaml* /app/pnpm-workspace.yaml* ./
|
COPY --from=builder /app/package.json ./
|
||||||
COPY --from=builder /app/mana-core-auth/package.json ./mana-core-auth/
|
|
||||||
|
|
||||||
# Install production dependencies only
|
# Install production dependencies + tsx for migrations
|
||||||
RUN pnpm install --prod --frozen-lockfile
|
RUN pnpm install --prod && pnpm add tsx
|
||||||
|
|
||||||
# Copy built application
|
# Copy built application
|
||||||
COPY --from=builder /app/mana-core-auth/dist ./mana-core-auth/dist
|
COPY --from=builder /app/dist ./dist
|
||||||
COPY --from=builder /app/mana-core-auth/src/db ./mana-core-auth/src/db
|
COPY --from=builder /app/src/db ./src/db
|
||||||
|
COPY mana-core-auth/drizzle.config.ts ./
|
||||||
|
COPY mana-core-auth/docker-entrypoint.sh ./
|
||||||
|
|
||||||
# Set working directory to the app
|
# Make entrypoint executable
|
||||||
WORKDIR /app/mana-core-auth
|
RUN chmod +x ./docker-entrypoint.sh
|
||||||
|
|
||||||
# Create non-root user
|
# Create non-root user
|
||||||
RUN addgroup -g 1001 -S nodejs && \
|
RUN addgroup -g 1001 -S nodejs && \
|
||||||
|
|
@ -59,5 +60,5 @@ EXPOSE 3001
|
||||||
HEALTHCHECK --interval=30s --timeout=3s --start-period=40s --retries=3 \
|
HEALTHCHECK --interval=30s --timeout=3s --start-period=40s --retries=3 \
|
||||||
CMD node -e "require('http').get('http://localhost:3001/api/v1/health', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})"
|
CMD node -e "require('http').get('http://localhost:3001/api/v1/health', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})"
|
||||||
|
|
||||||
# Start the application
|
# Start the application with entrypoint that runs migrations
|
||||||
CMD ["node", "dist/main.js"]
|
ENTRYPOINT ["./docker-entrypoint.sh"]
|
||||||
|
|
|
||||||
218
mana-core-auth/MIGRATIONS.md
Normal file
218
mana-core-auth/MIGRATIONS.md
Normal file
|
|
@ -0,0 +1,218 @@
|
||||||
|
# Database Migrations - Mana Core Auth
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
This project uses **Drizzle ORM** for database schema management with automatic migration support in Docker.
|
||||||
|
|
||||||
|
## Automatic Migration System
|
||||||
|
|
||||||
|
### 🐳 Docker (Production)
|
||||||
|
|
||||||
|
When you run `docker-compose up`, migrations are **automatically applied** before the service starts:
|
||||||
|
|
||||||
|
1. The `docker-entrypoint.sh` script runs `pnpm db:push --force`
|
||||||
|
2. This syncs the Drizzle schema to PostgreSQL
|
||||||
|
3. The application starts only after migrations succeed
|
||||||
|
|
||||||
|
**No manual intervention needed!**
|
||||||
|
|
||||||
|
### 💻 Local Development
|
||||||
|
|
||||||
|
For local development, you have two options:
|
||||||
|
|
||||||
|
#### Option 1: Automatic Schema Sync (Recommended)
|
||||||
|
```bash
|
||||||
|
# Sync schema to database (creates/updates tables)
|
||||||
|
pnpm db:push
|
||||||
|
```
|
||||||
|
|
||||||
|
This is the **fastest** way during development. It pushes your schema changes directly to the database without generating migration files.
|
||||||
|
|
||||||
|
#### Option 2: Generated Migrations (Production-style)
|
||||||
|
```bash
|
||||||
|
# 1. Generate migration files from schema changes
|
||||||
|
pnpm migration:generate
|
||||||
|
|
||||||
|
# 2. Apply migrations to database
|
||||||
|
pnpm migration:run
|
||||||
|
```
|
||||||
|
|
||||||
|
Use this approach when you want explicit migration files for version control.
|
||||||
|
|
||||||
|
## Commands Reference
|
||||||
|
|
||||||
|
| Command | Description |
|
||||||
|
|---------|-------------|
|
||||||
|
| `pnpm db:push` | Sync schema to database (no migration files) |
|
||||||
|
| `pnpm db:studio` | Open Drizzle Studio to view/edit data |
|
||||||
|
| `pnpm migration:generate` | Generate migration files from schema |
|
||||||
|
| `pnpm migration:run` | Apply pending migrations |
|
||||||
|
|
||||||
|
## How It Works
|
||||||
|
|
||||||
|
### Schema Location
|
||||||
|
All database tables are defined in TypeScript:
|
||||||
|
```
|
||||||
|
src/db/schema/
|
||||||
|
├── auth.schema.ts # Users, sessions, passwords, etc.
|
||||||
|
├── credits.schema.ts # Credit system tables
|
||||||
|
└── index.ts # Export all schemas
|
||||||
|
```
|
||||||
|
|
||||||
|
### Migration Flow
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
graph LR
|
||||||
|
A[Edit Schema] --> B{Environment?}
|
||||||
|
B -->|Development| C[pnpm db:push]
|
||||||
|
B -->|Production| D[pnpm migration:generate]
|
||||||
|
D --> E[pnpm migration:run]
|
||||||
|
C --> F[Tables Updated]
|
||||||
|
E --> F
|
||||||
|
```
|
||||||
|
|
||||||
|
### Docker Entrypoint Script
|
||||||
|
|
||||||
|
The `docker-entrypoint.sh` script ensures migrations run before the app starts:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
#!/bin/sh
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "🔄 Running database migrations..."
|
||||||
|
pnpm db:push --force
|
||||||
|
echo "✅ Migrations complete"
|
||||||
|
|
||||||
|
echo "🚀 Starting Mana Core Auth..."
|
||||||
|
exec node dist/main.js
|
||||||
|
```
|
||||||
|
|
||||||
|
## First-Time Setup
|
||||||
|
|
||||||
|
When starting fresh:
|
||||||
|
|
||||||
|
1. **Start PostgreSQL**:
|
||||||
|
```bash
|
||||||
|
docker compose up postgres -d
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Apply Schema**:
|
||||||
|
```bash
|
||||||
|
pnpm db:push
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Start Service**:
|
||||||
|
```bash
|
||||||
|
pnpm start:dev
|
||||||
|
```
|
||||||
|
|
||||||
|
## Production Deployment
|
||||||
|
|
||||||
|
When deploying with Docker Compose:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Migrations run automatically on container startup
|
||||||
|
docker compose up -d mana-core-auth
|
||||||
|
```
|
||||||
|
|
||||||
|
The service will:
|
||||||
|
1. Wait for PostgreSQL to be healthy (`depends_on`)
|
||||||
|
2. Run migrations via entrypoint script
|
||||||
|
3. Start the NestJS application
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### "relation does not exist"
|
||||||
|
**Problem**: Schema not synced to database
|
||||||
|
|
||||||
|
**Solution**:
|
||||||
|
```bash
|
||||||
|
pnpm db:push
|
||||||
|
```
|
||||||
|
|
||||||
|
### "schema already exists"
|
||||||
|
**Problem**: Partial migration state
|
||||||
|
|
||||||
|
**Solution**:
|
||||||
|
```bash
|
||||||
|
# Option 1: Force push
|
||||||
|
pnpm db:push --force
|
||||||
|
|
||||||
|
# Option 2: Reset database (⚠️ deletes all data)
|
||||||
|
docker compose down -v
|
||||||
|
docker compose up postgres -d
|
||||||
|
pnpm db:push
|
||||||
|
```
|
||||||
|
|
||||||
|
### Migration fails in Docker
|
||||||
|
**Problem**: Database credentials or connection
|
||||||
|
|
||||||
|
**Solution**:
|
||||||
|
Check `docker-compose.yml` environment variables:
|
||||||
|
- `DATABASE_URL`
|
||||||
|
- `POSTGRES_PASSWORD`
|
||||||
|
|
||||||
|
## Best Practices
|
||||||
|
|
||||||
|
### Development
|
||||||
|
- ✅ Use `pnpm db:push` for fast iteration
|
||||||
|
- ✅ Use Drizzle Studio to inspect data: `pnpm db:studio`
|
||||||
|
- ❌ Don't commit generated migration files during active development
|
||||||
|
|
||||||
|
### Production
|
||||||
|
- ✅ Let Docker handle migrations automatically
|
||||||
|
- ✅ Monitor container logs for migration success
|
||||||
|
- ✅ Ensure DATABASE_URL is correct in environment
|
||||||
|
|
||||||
|
### Schema Changes
|
||||||
|
- ✅ Make schema changes in `src/db/schema/*.ts`
|
||||||
|
- ✅ Test locally with `pnpm db:push`
|
||||||
|
- ✅ Commit schema changes to git
|
||||||
|
- ✅ Docker will auto-apply on deployment
|
||||||
|
|
||||||
|
## Migration Strategy
|
||||||
|
|
||||||
|
This project uses **"push-based migrations"** rather than explicit migration files:
|
||||||
|
|
||||||
|
| Approach | When to Use |
|
||||||
|
|----------|-------------|
|
||||||
|
| **Push (`db:push`)** | Development, Docker, quick iteration |
|
||||||
|
| **Generated Migrations** | When you need explicit SQL files, audit trail |
|
||||||
|
|
||||||
|
The push-based approach is **simpler** and **faster** for most use cases, which is why it's used in the Docker entrypoint.
|
||||||
|
|
||||||
|
## Environment Variables
|
||||||
|
|
||||||
|
Required for migrations:
|
||||||
|
|
||||||
|
```env
|
||||||
|
DATABASE_URL=postgresql://user:password@host:5432/dbname
|
||||||
|
```
|
||||||
|
|
||||||
|
In Docker Compose, this is auto-configured:
|
||||||
|
```yaml
|
||||||
|
DATABASE_URL: postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@pgbouncer:6432/${POSTGRES_DB}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Health Checks
|
||||||
|
|
||||||
|
The service won't start until:
|
||||||
|
1. ✅ PostgreSQL is healthy
|
||||||
|
2. ✅ Migrations complete successfully
|
||||||
|
3. ✅ Application boots without errors
|
||||||
|
|
||||||
|
Check container logs:
|
||||||
|
```bash
|
||||||
|
docker logs manacore-auth
|
||||||
|
```
|
||||||
|
|
||||||
|
Look for:
|
||||||
|
```
|
||||||
|
🔄 Running database migrations...
|
||||||
|
✅ Migrations complete
|
||||||
|
🚀 Starting Mana Core Auth...
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Status**: ✅ Automatic migrations configured and ready to use!
|
||||||
13
mana-core-auth/docker-entrypoint.sh
Executable file
13
mana-core-auth/docker-entrypoint.sh
Executable file
|
|
@ -0,0 +1,13 @@
|
||||||
|
#!/bin/sh
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "🔄 Running database migrations..."
|
||||||
|
|
||||||
|
# Run actual migrations (creates schemas + tables)
|
||||||
|
pnpm migration:run
|
||||||
|
|
||||||
|
echo "✅ Migrations complete"
|
||||||
|
|
||||||
|
# Start the application
|
||||||
|
echo "🚀 Starting Mana Core Auth..."
|
||||||
|
exec node dist/main.js
|
||||||
|
|
@ -22,38 +22,39 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nestjs/common": "^10.4.15",
|
"@nestjs/common": "^10.4.15",
|
||||||
|
"@nestjs/config": "^3.3.0",
|
||||||
"@nestjs/core": "^10.4.15",
|
"@nestjs/core": "^10.4.15",
|
||||||
"@nestjs/platform-express": "^10.4.15",
|
"@nestjs/platform-express": "^10.4.15",
|
||||||
"@nestjs/config": "^3.3.0",
|
|
||||||
"@nestjs/throttler": "^6.2.1",
|
"@nestjs/throttler": "^6.2.1",
|
||||||
"better-auth": "^1.1.1",
|
|
||||||
"drizzle-orm": "^0.38.3",
|
|
||||||
"drizzle-kit": "^0.30.2",
|
|
||||||
"postgres": "^3.4.5",
|
|
||||||
"stripe": "^17.5.0",
|
|
||||||
"redis": "^4.7.0",
|
|
||||||
"bcrypt": "^5.1.1",
|
"bcrypt": "^5.1.1",
|
||||||
"nanoid": "^5.0.9",
|
"better-auth": "^1.1.1",
|
||||||
"zod": "^3.24.1",
|
|
||||||
"class-validator": "^0.14.1",
|
|
||||||
"class-transformer": "^0.5.1",
|
"class-transformer": "^0.5.1",
|
||||||
"jsonwebtoken": "^9.0.2",
|
"class-validator": "^0.14.1",
|
||||||
"winston": "^3.17.0",
|
|
||||||
"helmet": "^8.0.0",
|
|
||||||
"cookie-parser": "^1.4.7",
|
"cookie-parser": "^1.4.7",
|
||||||
|
"dotenv": "^16.4.7",
|
||||||
|
"drizzle-kit": "^0.30.2",
|
||||||
|
"drizzle-orm": "^0.38.3",
|
||||||
|
"helmet": "^8.0.0",
|
||||||
|
"jsonwebtoken": "^9.0.2",
|
||||||
|
"nanoid": "^5.0.9",
|
||||||
|
"postgres": "^3.4.5",
|
||||||
|
"redis": "^4.7.0",
|
||||||
"reflect-metadata": "^0.2.2",
|
"reflect-metadata": "^0.2.2",
|
||||||
"rxjs": "^7.8.1"
|
"rxjs": "^7.8.1",
|
||||||
|
"stripe": "^17.5.0",
|
||||||
|
"winston": "^3.17.0",
|
||||||
|
"zod": "^3.24.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@nestjs/cli": "^11.0.0",
|
"@nestjs/cli": "^11.0.0",
|
||||||
"@nestjs/schematics": "^11.0.0",
|
"@nestjs/schematics": "^11.0.0",
|
||||||
"@nestjs/testing": "^10.4.15",
|
"@nestjs/testing": "^10.4.15",
|
||||||
"@types/express": "^5.0.0",
|
|
||||||
"@types/node": "^22.10.2",
|
|
||||||
"@types/bcrypt": "^5.0.2",
|
"@types/bcrypt": "^5.0.2",
|
||||||
"@types/jsonwebtoken": "^9.0.7",
|
|
||||||
"@types/cookie-parser": "^1.4.7",
|
"@types/cookie-parser": "^1.4.7",
|
||||||
|
"@types/express": "^5.0.0",
|
||||||
"@types/jest": "^29.5.14",
|
"@types/jest": "^29.5.14",
|
||||||
|
"@types/jsonwebtoken": "^9.0.7",
|
||||||
|
"@types/node": "^22.10.2",
|
||||||
"@types/supertest": "^6.0.2",
|
"@types/supertest": "^6.0.2",
|
||||||
"@typescript-eslint/eslint-plugin": "^8.18.2",
|
"@typescript-eslint/eslint-plugin": "^8.18.2",
|
||||||
"@typescript-eslint/parser": "^8.18.2",
|
"@typescript-eslint/parser": "^8.18.2",
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,16 @@
|
||||||
import { Injectable, UnauthorizedException, ConflictException, BadRequestException } from '@nestjs/common';
|
import { Injectable, UnauthorizedException, ConflictException, BadRequestException } from '@nestjs/common';
|
||||||
import { ConfigService } from '@nestjs/config';
|
import { ConfigService } from '@nestjs/config';
|
||||||
import { eq, and } from 'drizzle-orm';
|
import { eq, and, isNull } from 'drizzle-orm';
|
||||||
import * as bcrypt from 'bcrypt';
|
import * as bcrypt from 'bcrypt';
|
||||||
import * as jwt from 'jsonwebtoken';
|
import * as jwt from 'jsonwebtoken';
|
||||||
import { nanoid } from 'nanoid';
|
import { nanoid } from 'nanoid';
|
||||||
|
import { randomUUID } from 'crypto';
|
||||||
import { getDb } from '../db/connection';
|
import { getDb } from '../db/connection';
|
||||||
import { users, passwords, sessions } from '../db/schema';
|
import { users, passwords, sessions } from '../db/schema';
|
||||||
import { RegisterDto } from './dto/register.dto';
|
import { RegisterDto } from './dto/register.dto';
|
||||||
import { LoginDto } from './dto/login.dto';
|
import { LoginDto } from './dto/login.dto';
|
||||||
|
|
||||||
interface TokenPayload {
|
export interface TokenPayload {
|
||||||
sub: string;
|
sub: string;
|
||||||
email: string;
|
email: string;
|
||||||
role: string;
|
role: string;
|
||||||
|
|
@ -136,7 +137,7 @@ export class AuthService {
|
||||||
const [session] = await db
|
const [session] = await db
|
||||||
.select()
|
.select()
|
||||||
.from(sessions)
|
.from(sessions)
|
||||||
.where(and(eq(sessions.refreshToken, refreshToken), eq(sessions.revokedAt, null)))
|
.where(and(eq(sessions.refreshToken, refreshToken), isNull(sessions.revokedAt)))
|
||||||
.limit(1);
|
.limit(1);
|
||||||
|
|
||||||
if (!session) {
|
if (!session) {
|
||||||
|
|
@ -166,8 +167,8 @@ export class AuthService {
|
||||||
user.id,
|
user.id,
|
||||||
user.email,
|
user.email,
|
||||||
user.role,
|
user.role,
|
||||||
session.deviceId,
|
session.deviceId ?? undefined,
|
||||||
session.deviceName,
|
session.deviceName ?? undefined,
|
||||||
ipAddress,
|
ipAddress,
|
||||||
userAgent,
|
userAgent,
|
||||||
);
|
);
|
||||||
|
|
@ -205,14 +206,18 @@ export class AuthService {
|
||||||
) {
|
) {
|
||||||
const db = this.getDb();
|
const db = this.getDb();
|
||||||
|
|
||||||
const privateKey = this.configService.get<string>('jwt.privateKey');
|
const privateKeyRaw = this.configService.get<string>('jwt.privateKey');
|
||||||
|
if (!privateKeyRaw) {
|
||||||
|
throw new Error('JWT private key not configured');
|
||||||
|
}
|
||||||
|
const privateKey: string = privateKeyRaw;
|
||||||
const accessTokenExpiry = this.configService.get<string>('jwt.accessTokenExpiry') || '15m';
|
const accessTokenExpiry = this.configService.get<string>('jwt.accessTokenExpiry') || '15m';
|
||||||
const refreshTokenExpiry = this.configService.get<string>('jwt.refreshTokenExpiry') || '7d';
|
const refreshTokenExpiry = this.configService.get<string>('jwt.refreshTokenExpiry') || '7d';
|
||||||
const issuer = this.configService.get<string>('jwt.issuer');
|
const issuer = this.configService.get<string>('jwt.issuer');
|
||||||
const audience = this.configService.get<string>('jwt.audience');
|
const audience = this.configService.get<string>('jwt.audience');
|
||||||
|
|
||||||
// Generate session ID
|
// Generate session ID (must be UUID for database)
|
||||||
const sessionId = nanoid();
|
const sessionId = randomUUID();
|
||||||
|
|
||||||
// Create session record
|
// Create session record
|
||||||
const refreshTokenString = nanoid(64);
|
const refreshTokenString = nanoid(64);
|
||||||
|
|
@ -233,7 +238,7 @@ export class AuthService {
|
||||||
});
|
});
|
||||||
|
|
||||||
// Generate JWT payload
|
// Generate JWT payload
|
||||||
const tokenPayload: TokenPayload = {
|
const tokenPayload: Record<string, unknown> = {
|
||||||
sub: userId,
|
sub: userId,
|
||||||
email,
|
email,
|
||||||
role,
|
role,
|
||||||
|
|
@ -243,10 +248,10 @@ export class AuthService {
|
||||||
|
|
||||||
// Sign access token
|
// Sign access token
|
||||||
const accessToken = jwt.sign(tokenPayload, privateKey, {
|
const accessToken = jwt.sign(tokenPayload, privateKey, {
|
||||||
algorithm: 'RS256',
|
algorithm: 'RS256' as const,
|
||||||
expiresIn: accessTokenExpiry,
|
expiresIn: accessTokenExpiry as jwt.SignOptions['expiresIn'],
|
||||||
issuer,
|
...(issuer && { issuer }),
|
||||||
audience,
|
...(audience && { audience }),
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
@ -260,6 +265,9 @@ export class AuthService {
|
||||||
async validateToken(token: string) {
|
async validateToken(token: string) {
|
||||||
try {
|
try {
|
||||||
const publicKey = this.configService.get<string>('jwt.publicKey');
|
const publicKey = this.configService.get<string>('jwt.publicKey');
|
||||||
|
if (!publicKey) {
|
||||||
|
throw new Error('JWT public key not configured');
|
||||||
|
}
|
||||||
const audience = this.configService.get<string>('jwt.audience');
|
const audience = this.configService.get<string>('jwt.audience');
|
||||||
const issuer = this.configService.get<string>('jwt.issuer');
|
const issuer = this.configService.get<string>('jwt.issuer');
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,9 @@ export class JwtAuthGuard implements CanActivate {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const publicKey = this.configService.get<string>('jwt.publicKey');
|
const publicKey = this.configService.get<string>('jwt.publicKey');
|
||||||
|
if (!publicKey) {
|
||||||
|
throw new UnauthorizedException('JWT configuration error');
|
||||||
|
}
|
||||||
const audience = this.configService.get<string>('jwt.audience');
|
const audience = this.configService.get<string>('jwt.audience');
|
||||||
const issuer = this.configService.get<string>('jwt.issuer');
|
const issuer = this.configService.get<string>('jwt.issuer');
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,10 @@
|
||||||
|
import { config } from 'dotenv';
|
||||||
import { migrate } from 'drizzle-orm/postgres-js/migrator';
|
import { migrate } from 'drizzle-orm/postgres-js/migrator';
|
||||||
import { getDb, closeConnection } from './connection';
|
import { getDb, closeConnection } from './connection';
|
||||||
|
|
||||||
|
// Load environment variables
|
||||||
|
config();
|
||||||
|
|
||||||
async function runMigrations() {
|
async function runMigrations() {
|
||||||
const databaseUrl = process.env.DATABASE_URL;
|
const databaseUrl = process.env.DATABASE_URL;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import { NestFactory } from '@nestjs/core';
|
||||||
import { ValidationPipe } from '@nestjs/common';
|
import { ValidationPipe } from '@nestjs/common';
|
||||||
import { ConfigService } from '@nestjs/config';
|
import { ConfigService } from '@nestjs/config';
|
||||||
import helmet from 'helmet';
|
import helmet from 'helmet';
|
||||||
import * as cookieParser from 'cookie-parser';
|
import cookieParser from 'cookie-parser';
|
||||||
import { AppModule } from './app.module';
|
import { AppModule } from './app.module';
|
||||||
|
|
||||||
async function bootstrap() {
|
async function bootstrap() {
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
"target": "ES2021",
|
"target": "ES2021",
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"outDir": "./dist",
|
"outDir": "./dist",
|
||||||
"baseUrl": "./",
|
"baseUrl": "../",
|
||||||
"incremental": true,
|
"incremental": true,
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"strictNullChecks": true,
|
"strictNullChecks": true,
|
||||||
|
|
@ -20,7 +20,8 @@
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"paths": {
|
"paths": {
|
||||||
"@/*": ["src/*"]
|
"@/*": ["mana-core-auth/src/*"],
|
||||||
|
"@manacore/*": ["packages/*/src"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"include": ["src/**/*"],
|
"include": ["src/**/*"],
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue