feat(auth): Implementiere vollständiges Auth-System
- JWT Access + Refresh Tokens mit djwt - Argon2 Password Hashing (OWASP konfig) - Rate Limiting für Auth-Endpoints - Rollen-basierte Zugriffskontrolle (owner, admin, manager, user) - DSGVO Audit Logging - Email-Verifizierung (Struktur) - Passwort-Reset Flow - Multi-Device Logout Neue Dateien: - src/types/index.ts - TypeScript Interfaces - src/db/connection.ts - PostgreSQL Pool - src/services/password.ts - Argon2 Hashing - src/services/jwt.ts - Token Generation - src/services/audit.ts - DSGVO Audit Log - src/middleware/auth.ts - Auth Middleware - src/repositories/user.ts - User DB Queries - src/repositories/organization.ts - Org DB Queries - src/utils/response.ts - API Response Helpers Task: #8 Authentifizierung & Benutzerverwaltung
This commit is contained in:
246
src/repositories/user.ts
Normal file
246
src/repositories/user.ts
Normal file
@@ -0,0 +1,246 @@
|
||||
import { query, queryOne, execute, transaction } from "../db/connection.ts";
|
||||
import type { User, Organization, RefreshTokenRecord } from "../types/index.ts";
|
||||
|
||||
// ============================================
|
||||
// USER REPOSITORY
|
||||
// ============================================
|
||||
|
||||
/**
|
||||
* Find user by email
|
||||
*/
|
||||
export async function findByEmail(email: string): Promise<User | null> {
|
||||
return await queryOne<User>(
|
||||
`SELECT * FROM users WHERE email = $1 AND deleted_at IS NULL`,
|
||||
[email.toLowerCase()]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find user by ID
|
||||
*/
|
||||
export async function findById(id: string): Promise<User | null> {
|
||||
return await queryOne<User>(
|
||||
`SELECT * FROM users WHERE id = $1 AND deleted_at IS NULL`,
|
||||
[id]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find user by verification token
|
||||
*/
|
||||
export async function findByVerificationToken(token: string): Promise<User | null> {
|
||||
return await queryOne<User>(
|
||||
`SELECT * FROM users WHERE verification_token = $1 AND deleted_at IS NULL`,
|
||||
[token]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find user by password reset token
|
||||
*/
|
||||
export async function findByResetToken(token: string): Promise<User | null> {
|
||||
return await queryOne<User>(
|
||||
`SELECT * FROM users
|
||||
WHERE reset_token = $1
|
||||
AND reset_token_expires > NOW()
|
||||
AND deleted_at IS NULL`,
|
||||
[token]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new user with organization
|
||||
*/
|
||||
export async function createUserWithOrg(data: {
|
||||
email: string;
|
||||
passwordHash: string;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
orgName: string;
|
||||
verificationToken: string;
|
||||
}): Promise<{ user: User; organization: Organization }> {
|
||||
return await transaction(async (conn) => {
|
||||
// Generate slug from org name
|
||||
const slug = data.orgName
|
||||
.toLowerCase()
|
||||
.replace(/[^a-z0-9]+/g, "-")
|
||||
.replace(/^-|-$/g, "")
|
||||
.substring(0, 50);
|
||||
|
||||
// Create organization
|
||||
const orgResult = await conn.queryObject<Organization>(
|
||||
`INSERT INTO organizations (name, slug, plan, max_users, settings)
|
||||
VALUES ($1, $2, 'free', 3, '{}')
|
||||
RETURNING *`,
|
||||
[data.orgName, slug + "-" + Date.now().toString(36)]
|
||||
);
|
||||
const organization = orgResult.rows[0];
|
||||
|
||||
// Create user as owner
|
||||
const userResult = await conn.queryObject<User>(
|
||||
`INSERT INTO users (
|
||||
org_id, email, password_hash, first_name, last_name,
|
||||
role, is_verified, verification_token
|
||||
)
|
||||
VALUES ($1, $2, $3, $4, $5, 'owner', false, $6)
|
||||
RETURNING *`,
|
||||
[
|
||||
organization.id,
|
||||
data.email.toLowerCase(),
|
||||
data.passwordHash,
|
||||
data.firstName,
|
||||
data.lastName,
|
||||
data.verificationToken,
|
||||
]
|
||||
);
|
||||
const user = userResult.rows[0];
|
||||
|
||||
return { user, organization };
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a user in an existing organization (invited user)
|
||||
*/
|
||||
export async function createUser(data: {
|
||||
orgId: string;
|
||||
email: string;
|
||||
passwordHash: string;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
role: "admin" | "manager" | "user";
|
||||
isVerified?: boolean;
|
||||
}): Promise<User> {
|
||||
const rows = await query<User>(
|
||||
`INSERT INTO users (
|
||||
org_id, email, password_hash, first_name, last_name,
|
||||
role, is_verified
|
||||
)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7)
|
||||
RETURNING *`,
|
||||
[
|
||||
data.orgId,
|
||||
data.email.toLowerCase(),
|
||||
data.passwordHash,
|
||||
data.firstName,
|
||||
data.lastName,
|
||||
data.role,
|
||||
data.isVerified ?? false,
|
||||
]
|
||||
);
|
||||
return rows[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Update user's email verification status
|
||||
*/
|
||||
export async function verifyEmail(userId: string): Promise<void> {
|
||||
await execute(
|
||||
`UPDATE users
|
||||
SET is_verified = true, verification_token = NULL, updated_at = NOW()
|
||||
WHERE id = $1`,
|
||||
[userId]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set password reset token
|
||||
*/
|
||||
export async function setResetToken(
|
||||
userId: string,
|
||||
token: string,
|
||||
expiresAt: Date
|
||||
): Promise<void> {
|
||||
await execute(
|
||||
`UPDATE users
|
||||
SET reset_token = $1, reset_token_expires = $2, updated_at = NOW()
|
||||
WHERE id = $3`,
|
||||
[token, expiresAt, userId]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update password and clear reset token
|
||||
*/
|
||||
export async function updatePassword(
|
||||
userId: string,
|
||||
passwordHash: string
|
||||
): Promise<void> {
|
||||
await execute(
|
||||
`UPDATE users
|
||||
SET password_hash = $1, reset_token = NULL, reset_token_expires = NULL, updated_at = NOW()
|
||||
WHERE id = $2`,
|
||||
[passwordHash, userId]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update last login timestamp
|
||||
*/
|
||||
export async function updateLastLogin(userId: string): Promise<void> {
|
||||
await execute(
|
||||
`UPDATE users SET last_login_at = NOW() WHERE id = $1`,
|
||||
[userId]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Store refresh token hash
|
||||
*/
|
||||
export async function storeRefreshToken(
|
||||
userId: string,
|
||||
tokenHash: string,
|
||||
expiresAt: Date
|
||||
): Promise<void> {
|
||||
await execute(
|
||||
`INSERT INTO refresh_tokens (user_id, token_hash, expires_at)
|
||||
VALUES ($1, $2, $3)`,
|
||||
[userId, tokenHash, expiresAt]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find refresh token by hash
|
||||
*/
|
||||
export async function findRefreshToken(tokenHash: string): Promise<RefreshTokenRecord | null> {
|
||||
return await queryOne<RefreshTokenRecord>(
|
||||
`SELECT * FROM refresh_tokens
|
||||
WHERE token_hash = $1
|
||||
AND revoked = false
|
||||
AND expires_at > NOW()`,
|
||||
[tokenHash]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Revoke a refresh token
|
||||
*/
|
||||
export async function revokeRefreshToken(tokenHash: string): Promise<void> {
|
||||
await execute(
|
||||
`UPDATE refresh_tokens
|
||||
SET revoked = true, revoked_at = NOW()
|
||||
WHERE token_hash = $1`,
|
||||
[tokenHash]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Revoke all refresh tokens for a user (logout everywhere)
|
||||
*/
|
||||
export async function revokeAllUserTokens(userId: string): Promise<void> {
|
||||
await execute(
|
||||
`UPDATE refresh_tokens
|
||||
SET revoked = true, revoked_at = NOW()
|
||||
WHERE user_id = $1 AND revoked = false`,
|
||||
[userId]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up expired refresh tokens
|
||||
*/
|
||||
export async function cleanupExpiredTokens(): Promise<number> {
|
||||
return await execute(
|
||||
`DELETE FROM refresh_tokens WHERE expires_at < NOW()`
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user