Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,7 @@ node_modules/
dist/
.env
*.tsbuildinfo

ios
# TeamD config build artifacts
teamd.config.mjs
teamd.config.*.map
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
"src/web-user",
"src/api",
"src/mobile",
"src/shared"
"src/shared",
"src/database"
],
"packageManager": "pnpm@10.18.0",
"scripts": {
Expand Down
1,689 changes: 1,311 additions & 378 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

6 changes: 4 additions & 2 deletions src/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,13 @@
"@teamd/database": "workspace:*",
"drizzle-orm": "^0.30.10",
"fastify": "^5.6.2",
"jsonwebtoken": "^9.0.2"
"fastify-raw-body": "^5.0.0",
"jsonwebtoken": "^9.0.2",
"stripe": "^20.0.0"
},
"devDependencies": {
"@types/jsonwebtoken": "^9.0.10",
"@types/node": "^20.19.24",
"@types/node": "^20.19.25",
"tsx": "^4.20.6",
"typescript": "^5.9.3"
}
Expand Down
20 changes: 18 additions & 2 deletions src/api/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@
import Fastify from 'fastify'
import cors from '@fastify/cors'
import cookie from '@fastify/cookie'
import rawBody from 'fastify-raw-body'
import TeamDConfig from '../../../teamd.config.mjs'

import 'dotenv/config';
// Import routes
import { authRoutes } from './routes/auth.js'
import { healthRoutes } from './routes/health.js'
import { userRoutes } from './routes/users.js'
import { instanceRoutes } from './routes/instances.js'
import { paymentsRoutes } from './routes/payments.js'

// Configuration from centralized config
const PORT = parseInt(process.env.PORT || String(TeamDConfig.api.port))
Expand All @@ -38,7 +40,12 @@ const fastify = Fastify({

// Register plugins
await fastify.register(cors, {
origin: [...TeamDConfig.cors.allowedOrigins],
origin: [
...TeamDConfig.cors.allowedOrigins,

'http://localhost:8081',
'http://127.0.0.1:8081',
],
credentials: true,
methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Authorization'],
Expand All @@ -48,11 +55,20 @@ await fastify.register(cookie, {
parseOptions: {},
})

// Make raw request body available on request.rawBody for webhook verification
await fastify.register(rawBody, {
field: 'rawBody',
global: true,
encoding: 'utf8',
runFirst: true,
})

// Register routes
await fastify.register(healthRoutes, { prefix: '/api' })
await fastify.register(authRoutes, { prefix: '/api' })
await fastify.register(userRoutes, { prefix: '/api' })
await fastify.register(instanceRoutes, { prefix: '/api' })
await fastify.register(paymentsRoutes, { prefix: '/api' })

// Root endpoint
fastify.get('/', async (request, reply) => {
Expand Down
118 changes: 52 additions & 66 deletions src/api/src/routes/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,110 +3,96 @@
*/

import type { FastifyInstance } from 'fastify'
import { findUserByEmail, generateToken } from '@large-event/api'
import { db, users } from '@large-event/database'
import { generateToken } from '@large-event/api'
import { successResponse, errorResponse, unauthorizedResponse } from '../utils/response.js'
import { requireAuth } from '../middleware/auth.js'


import { db, schema } from '@teamd/database'
import { eq } from 'drizzle-orm'

export async function authRoutes(fastify: FastifyInstance) {
/**
* POST /auth/login
* User login - creates user if doesn't exist (simplified auth for MVP)
*/
fastify.post<{
Body: { email: string; password?: string }
}>('/auth/login', async (request, reply) => {
const { email, password } = request.body

// ======================
// POST /auth/login
// ======================
fastify.post('/auth/login', async (request, reply) => {
const { email } = request.body

console.log("/auth/login called with:", email)

if (!email) {
return errorResponse(reply, 'Email is required', 400, 'VALIDATION_ERROR')
}

// Validate email format
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
if (!emailRegex.test(email)) {
return errorResponse(reply, 'Invalid email format', 400, 'VALIDATION_ERROR')
}

try {
// Find existing user - account must be pre-created
const user = await findUserByEmail(email)
console.log("Querying TeamD.users...")


const user = await db.query.users.findFirst({
where: eq(schema.users.email, email)
})

console.log("Query result:", user)

if (!user) {
console.log("No user found for:", email)
return errorResponse(reply, 'Account not found. Please contact an administrator.', 404, 'USER_NOT_FOUND')
}

// Generate JWT token (user already has isSystemAdmin from database)
console.log("Generating token for:", user.email)
const token = generateToken(user)

// Set HTTP-only cookie (shared across main portal and team dashboards)
reply.setCookie('auth-token', token, {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'lax',
path: '/',
maxAge: 24 * 60 * 60, // 24 hours
domain: process.env.NODE_ENV === 'production' ? '.large-event.com' : undefined
maxAge: 24 * 60 * 60,
})

return successResponse(reply, {
user,
token,
})
console.log("Login success for:", user.email)
return successResponse(reply, { user, token })

} catch (error) {
fastify.log.error('Login error:', error)
console.log(" ERROR inside login:", error)
return errorResponse(reply, 'Login failed', 500, 'LOGIN_ERROR')
}
})

/**
* POST /auth/logout
* Clear auth cookie
*/

// ======================
// POST /auth/logout
// ======================
fastify.post('/auth/logout', async (request, reply) => {
reply.clearCookie('auth-token', {
path: '/',
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'lax',
domain: process.env.NODE_ENV === 'production' ? '.large-event.com' : undefined
sameSite: 'lax'
})

return successResponse(reply, { message: 'Logged out successfully' })
})

/**
* GET /auth/me
* Get current authenticated user
*/
fastify.get(
'/auth/me',
{
preHandler: [requireAuth],
},
async (request, reply) => {
const user = request.user

if (!user) {
return unauthorizedResponse(reply)
}
// ======================
// GET /auth/me
// ======================
fastify.get('/auth/me', { preHandler: [requireAuth] }, async (request, reply) => {
const user = request.user
if (!user) return unauthorizedResponse(reply)
return successResponse(reply, { user })
})

return successResponse(reply, { user })
}
)

/**
* GET /auth/token
* Get current JWT token (for debugging/testing)
*/
fastify.get(
'/auth/token',
{
preHandler: [requireAuth],
},
async (request, reply) => {
const token = request.cookies['auth-token'] || request.headers.authorization?.substring(7)

return successResponse(reply, { token })
}
)

// ======================
// GET /auth/token
// ======================
fastify.get('/auth/token', { preHandler: [requireAuth] }, async (request, reply) => {
const token = request.cookies['auth-token']
|| request.headers.authorization?.substring(7)

return successResponse(reply, { token })
})
}
Loading