feat: user export service. bug and UX fixes. Complete minus outstanding email template fixes.
This commit is contained in:
@@ -182,4 +182,63 @@ export class AuthController {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /api/auth/security-status
|
||||
* Get user security status
|
||||
* Protected endpoint - requires JWT
|
||||
*/
|
||||
async getSecurityStatus(request: FastifyRequest, reply: FastifyReply) {
|
||||
try {
|
||||
const userId = (request as any).user.sub;
|
||||
|
||||
const result = await this.authService.getSecurityStatus(userId);
|
||||
|
||||
logger.info('Security status retrieved', {
|
||||
userId: userId.substring(0, 8) + '...',
|
||||
emailVerified: result.emailVerified,
|
||||
});
|
||||
|
||||
return reply.code(200).send(result);
|
||||
} catch (error: any) {
|
||||
logger.error('Failed to get security status', {
|
||||
error,
|
||||
userId: (request as any).user?.sub,
|
||||
});
|
||||
|
||||
return reply.code(500).send({
|
||||
error: 'Internal server error',
|
||||
message: 'Failed to get security status',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /api/auth/request-password-reset
|
||||
* Request password reset email
|
||||
* Protected endpoint - requires JWT
|
||||
*/
|
||||
async requestPasswordReset(request: FastifyRequest, reply: FastifyReply) {
|
||||
try {
|
||||
const userId = (request as any).user.sub;
|
||||
|
||||
const result = await this.authService.requestPasswordReset(userId);
|
||||
|
||||
logger.info('Password reset email requested', {
|
||||
userId: userId.substring(0, 8) + '...',
|
||||
});
|
||||
|
||||
return reply.code(200).send(result);
|
||||
} catch (error: any) {
|
||||
logger.error('Failed to request password reset', {
|
||||
error,
|
||||
userId: (request as any).user?.sub,
|
||||
});
|
||||
|
||||
return reply.code(500).send({
|
||||
error: 'Internal server error',
|
||||
message: 'Failed to send password reset email. Please try again later.',
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,4 +36,16 @@ export const authRoutes: FastifyPluginAsync = async (
|
||||
preHandler: [fastify.authenticate],
|
||||
handler: authController.getUserStatus.bind(authController),
|
||||
});
|
||||
|
||||
// GET /api/auth/security-status - Get security status (requires JWT)
|
||||
fastify.get('/auth/security-status', {
|
||||
preHandler: [fastify.authenticate],
|
||||
handler: authController.getSecurityStatus.bind(authController),
|
||||
});
|
||||
|
||||
// POST /api/auth/request-password-reset - Request password reset email (requires JWT)
|
||||
fastify.post('/auth/request-password-reset', {
|
||||
preHandler: [fastify.authenticate],
|
||||
handler: authController.requestPasswordReset.bind(authController),
|
||||
});
|
||||
};
|
||||
|
||||
@@ -11,6 +11,8 @@ import {
|
||||
SignupResponse,
|
||||
VerifyStatusResponse,
|
||||
ResendVerificationResponse,
|
||||
SecurityStatusResponse,
|
||||
PasswordResetResponse,
|
||||
} from './auth.types';
|
||||
|
||||
export class AuthService {
|
||||
@@ -210,4 +212,61 @@ export class AuthService {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get security status for the user
|
||||
* Returns email verification status and passkey info
|
||||
*/
|
||||
async getSecurityStatus(auth0Sub: string): Promise<SecurityStatusResponse> {
|
||||
try {
|
||||
// Get user details from Auth0
|
||||
const auth0User = await auth0ManagementClient.getUser(auth0Sub);
|
||||
|
||||
logger.info('Retrieved security status', {
|
||||
auth0Sub: auth0Sub.substring(0, 8) + '...',
|
||||
emailVerified: auth0User.emailVerified,
|
||||
});
|
||||
|
||||
return {
|
||||
emailVerified: auth0User.emailVerified,
|
||||
email: auth0User.email,
|
||||
// Passkeys are enabled at the Auth0 connection level, not per-user
|
||||
// This is informational - actual passkey enrollment happens in Auth0 Universal Login
|
||||
passkeysEnabled: true,
|
||||
// Auth0 doesn't expose password last changed date via Management API
|
||||
// Would require Auth0 Logs API or user_metadata to track this
|
||||
passwordLastChanged: null,
|
||||
};
|
||||
} catch (error) {
|
||||
logger.error('Failed to get security status', { auth0Sub, error });
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Request password reset email
|
||||
* Triggers Auth0 to send password reset email to user
|
||||
*/
|
||||
async requestPasswordReset(auth0Sub: string): Promise<PasswordResetResponse> {
|
||||
try {
|
||||
// Get user email from Auth0
|
||||
const auth0User = await auth0ManagementClient.getUser(auth0Sub);
|
||||
|
||||
// Send password reset email via Auth0
|
||||
await auth0ManagementClient.sendPasswordResetEmail(auth0User.email);
|
||||
|
||||
logger.info('Password reset email requested', {
|
||||
auth0Sub: auth0Sub.substring(0, 8) + '...',
|
||||
email: auth0User.email.substring(0, 3) + '***',
|
||||
});
|
||||
|
||||
return {
|
||||
message: 'Password reset email sent. Please check your inbox.',
|
||||
success: true,
|
||||
};
|
||||
} catch (error) {
|
||||
logger.error('Failed to request password reset', { auth0Sub, error });
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,3 +26,17 @@ export interface VerifyStatusResponse {
|
||||
export interface ResendVerificationResponse {
|
||||
message: string;
|
||||
}
|
||||
|
||||
// Response from security status endpoint
|
||||
export interface SecurityStatusResponse {
|
||||
emailVerified: boolean;
|
||||
email: string;
|
||||
passkeysEnabled: boolean;
|
||||
passwordLastChanged: string | null;
|
||||
}
|
||||
|
||||
// Response from password reset request endpoint
|
||||
export interface PasswordResetResponse {
|
||||
message: string;
|
||||
success: boolean;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user