Update paths in sync.sh, master/main.go, and CLAUDE.md to reflect the directory rename. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
232 lines
6.5 KiB
TypeScript
232 lines
6.5 KiB
TypeScript
// routes.ts
|
|
//
|
|
// Authentication route handlers.
|
|
|
|
import { z } from "zod";
|
|
import { contentTypes } from "../content-types";
|
|
import { httpCodes } from "../http-codes";
|
|
import { request } from "../request";
|
|
import type { Call, Result, Route } from "../types";
|
|
import {
|
|
forgotPasswordInputParser,
|
|
loginInputParser,
|
|
registerInputParser,
|
|
resetPasswordInputParser,
|
|
} from "./types";
|
|
|
|
// Helper for JSON responses
|
|
const jsonResponse = (
|
|
code: (typeof httpCodes.success)[keyof typeof httpCodes.success],
|
|
data: object,
|
|
): Result => ({
|
|
code,
|
|
contentType: contentTypes.application.json,
|
|
result: JSON.stringify(data),
|
|
});
|
|
|
|
const errorResponse = (
|
|
code: (typeof httpCodes.clientErrors)[keyof typeof httpCodes.clientErrors],
|
|
error: string,
|
|
): Result => ({
|
|
code,
|
|
contentType: contentTypes.application.json,
|
|
result: JSON.stringify({ error }),
|
|
});
|
|
|
|
// POST /auth/login
|
|
const loginHandler = async (call: Call): Promise<Result> => {
|
|
try {
|
|
const body = call.request.body;
|
|
const { email, password } = loginInputParser.parse(body);
|
|
|
|
const result = await request.auth.login(email, password, "cookie", {
|
|
userAgent: call.request.get("User-Agent"),
|
|
ipAddress: call.request.ip,
|
|
});
|
|
|
|
if (!result.success) {
|
|
return errorResponse(
|
|
httpCodes.clientErrors.Unauthorized,
|
|
result.error,
|
|
);
|
|
}
|
|
|
|
return jsonResponse(httpCodes.success.OK, {
|
|
token: result.token,
|
|
user: {
|
|
id: result.user.id,
|
|
email: result.user.email,
|
|
displayName: result.user.displayName,
|
|
},
|
|
});
|
|
} catch (error) {
|
|
if (error instanceof z.ZodError) {
|
|
return errorResponse(
|
|
httpCodes.clientErrors.BadRequest,
|
|
"Invalid input",
|
|
);
|
|
}
|
|
throw error;
|
|
}
|
|
};
|
|
|
|
// POST /auth/logout
|
|
const logoutHandler = async (call: Call): Promise<Result> => {
|
|
const token = request.auth.extractToken(call.request);
|
|
if (token) {
|
|
await request.auth.logout(token);
|
|
}
|
|
|
|
return jsonResponse(httpCodes.success.OK, { message: "Logged out" });
|
|
};
|
|
|
|
// POST /auth/register
|
|
const registerHandler = async (call: Call): Promise<Result> => {
|
|
try {
|
|
const body = call.request.body;
|
|
const { email, password, displayName } =
|
|
registerInputParser.parse(body);
|
|
|
|
const result = await request.auth.register(
|
|
email,
|
|
password,
|
|
displayName,
|
|
);
|
|
|
|
if (!result.success) {
|
|
return errorResponse(httpCodes.clientErrors.Conflict, result.error);
|
|
}
|
|
|
|
// TODO: Send verification email with result.verificationToken
|
|
// For now, log it for development
|
|
console.log(
|
|
`[AUTH] Verification token for ${email}: ${result.verificationToken}`,
|
|
);
|
|
|
|
return jsonResponse(httpCodes.success.Created, {
|
|
message:
|
|
"Registration successful. Please check your email to verify your account.",
|
|
user: {
|
|
id: result.user.id,
|
|
email: result.user.email,
|
|
},
|
|
});
|
|
} catch (error) {
|
|
if (error instanceof z.ZodError) {
|
|
return errorResponse(
|
|
httpCodes.clientErrors.BadRequest,
|
|
"Invalid input",
|
|
);
|
|
}
|
|
throw error;
|
|
}
|
|
};
|
|
|
|
// POST /auth/forgot-password
|
|
const forgotPasswordHandler = async (call: Call): Promise<Result> => {
|
|
try {
|
|
const body = call.request.body;
|
|
const { email } = forgotPasswordInputParser.parse(body);
|
|
|
|
const result = await request.auth.createPasswordResetToken(email);
|
|
|
|
// Always return success (don't reveal if email exists)
|
|
if (result) {
|
|
// TODO: Send password reset email
|
|
console.log(
|
|
`[AUTH] Password reset token for ${email}: ${result.token}`,
|
|
);
|
|
}
|
|
|
|
return jsonResponse(httpCodes.success.OK, {
|
|
message:
|
|
"If an account exists with that email, a password reset link has been sent.",
|
|
});
|
|
} catch (error) {
|
|
if (error instanceof z.ZodError) {
|
|
return errorResponse(
|
|
httpCodes.clientErrors.BadRequest,
|
|
"Invalid input",
|
|
);
|
|
}
|
|
throw error;
|
|
}
|
|
};
|
|
|
|
// POST /auth/reset-password
|
|
const resetPasswordHandler = async (call: Call): Promise<Result> => {
|
|
try {
|
|
const body = call.request.body;
|
|
const { token, password } = resetPasswordInputParser.parse(body);
|
|
|
|
const result = await request.auth.resetPassword(token, password);
|
|
|
|
if (!result.success) {
|
|
return errorResponse(
|
|
httpCodes.clientErrors.BadRequest,
|
|
result.error,
|
|
);
|
|
}
|
|
|
|
return jsonResponse(httpCodes.success.OK, {
|
|
message:
|
|
"Password has been reset. You can now log in with your new password.",
|
|
});
|
|
} catch (error) {
|
|
if (error instanceof z.ZodError) {
|
|
return errorResponse(
|
|
httpCodes.clientErrors.BadRequest,
|
|
"Invalid input",
|
|
);
|
|
}
|
|
throw error;
|
|
}
|
|
};
|
|
|
|
// GET /auth/verify-email?token=xxx
|
|
const verifyEmailHandler = async (call: Call): Promise<Result> => {
|
|
const url = new URL(call.path, "http://localhost");
|
|
const token = url.searchParams.get("token");
|
|
|
|
if (!token) {
|
|
return errorResponse(
|
|
httpCodes.clientErrors.BadRequest,
|
|
"Missing token",
|
|
);
|
|
}
|
|
|
|
const result = await request.auth.verifyEmail(token);
|
|
|
|
if (!result.success) {
|
|
return errorResponse(httpCodes.clientErrors.BadRequest, result.error);
|
|
}
|
|
|
|
return jsonResponse(httpCodes.success.OK, {
|
|
message: "Email verified successfully. You can now log in.",
|
|
});
|
|
};
|
|
|
|
// Export routes
|
|
const authRoutes: Route[] = [
|
|
{ path: "/auth/login", methods: ["POST"], handler: loginHandler },
|
|
{ path: "/auth/logout", methods: ["POST"], handler: logoutHandler },
|
|
{ path: "/auth/register", methods: ["POST"], handler: registerHandler },
|
|
{
|
|
path: "/auth/forgot-password",
|
|
methods: ["POST"],
|
|
handler: forgotPasswordHandler,
|
|
},
|
|
{
|
|
path: "/auth/reset-password",
|
|
methods: ["POST"],
|
|
handler: resetPasswordHandler,
|
|
},
|
|
{
|
|
path: "/auth/verify-email",
|
|
methods: ["GET"],
|
|
handler: verifyEmailHandler,
|
|
},
|
|
];
|
|
|
|
export { authRoutes };
|