- move config requiredVariables to contants
All checks were successful
Code quality checks / biome (push) Successful in 13s
All checks were successful
Code quality checks / biome (push) Successful in 13s
- change register email - add verify route ( mostly still needs to be tested
This commit is contained in:
parent
83b71f62cf
commit
fff3c3ca50
16 changed files with 482 additions and 89 deletions
|
@ -1,6 +1,7 @@
|
|||
import { echo } from "@atums/echo";
|
||||
import { validateJWTConfig, validateMailerConfig } from "#lib/validation";
|
||||
import { isValidUrl } from "#lib/validation/url";
|
||||
import { requiredVariables } from "./constants";
|
||||
import { cassandraConfig, validateCassandraConfig } from "./database/cassandra";
|
||||
import { jwt } from "./jwt";
|
||||
import { mailerConfig } from "./mailer";
|
||||
|
@ -12,30 +13,11 @@ const environment: Environment = {
|
|||
host: process.env.HOST || "0.0.0.0",
|
||||
development:
|
||||
process.env.NODE_ENV === "development" || process.argv.includes("--dev"),
|
||||
fqdn: process.env.FRONTEND_FQDN || "",
|
||||
frontendFqdn: process.env.FRONTEND_FQDN || "",
|
||||
backendFqdn: process.env.BACKEND_FQDN || "",
|
||||
};
|
||||
|
||||
function verifyRequiredVariables(): void {
|
||||
const requiredVariables = [
|
||||
"HOST",
|
||||
"PORT",
|
||||
|
||||
"REDIS_URL",
|
||||
"REDIS_TTL",
|
||||
|
||||
"CASSANDRA_HOST",
|
||||
"CASSANDRA_PORT",
|
||||
"CASSANDRA_CONTACT_POINTS",
|
||||
"CASSANDRA_AUTH_ENABLED",
|
||||
"CASSANDRA_DATACENTER",
|
||||
|
||||
"JWT_SECRET",
|
||||
"JWT_EXPIRATION",
|
||||
"JWT_ISSUER",
|
||||
|
||||
"FRONTEND_FQDN",
|
||||
];
|
||||
|
||||
let hasError = false;
|
||||
|
||||
for (const key of requiredVariables) {
|
||||
|
@ -56,9 +38,11 @@ function verifyRequiredVariables(): void {
|
|||
}
|
||||
|
||||
const validateJWT = validateJWTConfig(jwt);
|
||||
if (!validateJWT.valid) {
|
||||
if (!validateJWT.isValid) {
|
||||
echo.error("JWT configuration validation failed:");
|
||||
echo.error(`- ${validateJWT.error}`);
|
||||
for (const error of validateJWT.errors) {
|
||||
echo.error(`- ${error}`);
|
||||
}
|
||||
hasError = true;
|
||||
}
|
||||
|
||||
|
@ -71,19 +55,24 @@ function verifyRequiredVariables(): void {
|
|||
hasError = true;
|
||||
}
|
||||
|
||||
const urlValidation = isValidUrl(environment.fqdn, {
|
||||
requireProtocol: true,
|
||||
failOnTrailingSlash: true,
|
||||
allowedProtocols: ["http", "https"],
|
||||
allowLocalhost: environment.development,
|
||||
allowIP: environment.development,
|
||||
});
|
||||
const validateUrl = (url: string, name: string) => {
|
||||
const validation = isValidUrl(url, {
|
||||
requireProtocol: true,
|
||||
failOnTrailingSlash: true,
|
||||
allowedProtocols: ["http", "https"],
|
||||
allowLocalhost: environment.development,
|
||||
allowIP: environment.development,
|
||||
});
|
||||
|
||||
if (!urlValidation.valid) {
|
||||
echo.error("FRONTEND_FQDN validation failed:");
|
||||
echo.error(`- ${urlValidation.error}`);
|
||||
hasError = true;
|
||||
}
|
||||
if (!validation.valid) {
|
||||
echo.error(`${name} validation failed:`);
|
||||
echo.error(`- ${validation.error}`);
|
||||
hasError = true;
|
||||
}
|
||||
};
|
||||
|
||||
validateUrl(environment.frontendFqdn, "FRONTEND_FQDN");
|
||||
validateUrl(environment.backendFqdn, "BACKEND_FQDN");
|
||||
|
||||
if (hasError) {
|
||||
process.exit(1);
|
||||
|
|
|
@ -1,3 +1,32 @@
|
|||
export const requiredVariables = [
|
||||
"HOST",
|
||||
"PORT",
|
||||
|
||||
"REDIS_URL",
|
||||
"REDIS_TTL",
|
||||
|
||||
"CASSANDRA_HOST",
|
||||
"CASSANDRA_PORT",
|
||||
"CASSANDRA_CONTACT_POINTS",
|
||||
"CASSANDRA_DATACENTER",
|
||||
"CASSANDRA_KEYSPACE",
|
||||
|
||||
"JWT_SECRET",
|
||||
"JWT_EXPIRATION",
|
||||
"JWT_ISSUER",
|
||||
|
||||
"FRONTEND_FQDN",
|
||||
|
||||
"SMTP_ADDRESS",
|
||||
"SMTP_PORT",
|
||||
"SMTP_FROM",
|
||||
"SMTP_USERNAME",
|
||||
"SMTP_PASSWORD",
|
||||
|
||||
"EXTRA_COMPANY_NAME",
|
||||
"EXTRA_SUPPORT_EMAIL",
|
||||
];
|
||||
|
||||
export * from "./server";
|
||||
export * from "./validation";
|
||||
export * from "./database";
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
const reqLoggerIgnores = {
|
||||
ignoredStartsWith: ["/public"],
|
||||
ignoredPaths: [""],
|
||||
ignoredPaths: ["/favicon.ico"],
|
||||
};
|
||||
|
||||
export { reqLoggerIgnores };
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import type { CassandraConfig } from "#types/config";
|
||||
import type { simpleConfigValidation } from "#types/lib";
|
||||
|
||||
function isValidHost(host: string): boolean {
|
||||
if (!host || host.trim().length === 0) return false;
|
||||
|
@ -50,10 +51,9 @@ function isValidDatacenter(datacenter: string, authEnabled: boolean): boolean {
|
|||
return datacenter.trim().length > 0;
|
||||
}
|
||||
|
||||
function validateCassandraConfig(config: CassandraConfig): {
|
||||
isValid: boolean;
|
||||
errors: string[];
|
||||
} {
|
||||
function validateCassandraConfig(
|
||||
config: CassandraConfig,
|
||||
): simpleConfigValidation {
|
||||
const errors: string[] = [];
|
||||
|
||||
if (!isValidHost(config.host)) {
|
||||
|
|
6
src/environment/extra.ts
Normal file
6
src/environment/extra.ts
Normal file
|
@ -0,0 +1,6 @@
|
|||
const extraValues = {
|
||||
companyName: process.env.EXTRA_COMPANY_NAME || "Default Company",
|
||||
supportEmail: process.env.EXTRA_SUPPORT_EMAIL || "",
|
||||
};
|
||||
|
||||
export { extraValues };
|
|
@ -5,28 +5,95 @@
|
|||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{{subject}}</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
line-height: 1.6;
|
||||
background-color: #1a1a1a;
|
||||
color: #e0e0e0;
|
||||
}
|
||||
|
||||
h2 {
|
||||
color: #4a9eff;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #4a9eff;
|
||||
}
|
||||
|
||||
.button {
|
||||
display: inline-block;
|
||||
padding: 12px 24px;
|
||||
background-color: #2d7a2d;
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
border-radius: 4px;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.expiry-notice {
|
||||
background-color: #2a2a2a;
|
||||
border: 1px solid #444;
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
margin: 15px 0;
|
||||
color: #ffa500;
|
||||
}
|
||||
|
||||
.fallback-url {
|
||||
word-break: break-all;
|
||||
background-color: #2a2a2a;
|
||||
border: 1px solid #444;
|
||||
padding: 8px;
|
||||
border-radius: 4px;
|
||||
font-family: monospace;
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.footer {
|
||||
margin-top: 30px;
|
||||
padding-top: 20px;
|
||||
border-top: 1px solid #444;
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h2>Welcome to {{companyName}}!</h2>
|
||||
<p>Hi {{displayName}},</p>
|
||||
<p>Thank you for registering with {{companyName}}. Your account has been successfully created.</p>
|
||||
|
||||
<h2>Account Details:</h2>
|
||||
<p>
|
||||
<strong>User ID:</strong> {{id}}<br>
|
||||
<strong>Verification Status:</strong> {{isVerified}}
|
||||
</p>
|
||||
<p>Thanks for signing up! Please verify your email address to activate your account:</p>
|
||||
|
||||
<p>To get started, please verify your email address by clicking the link below:</p>
|
||||
<p><a href="{{verificationUrl}}">Verify Email Address</a></p>
|
||||
<p><a href="{{verificationUrl}}" class="button">Verify Email Address</a></p>
|
||||
|
||||
<div class="expiry-notice">
|
||||
<strong>⏰ Important:</strong> {{willExpire}} for security reasons.
|
||||
</div>
|
||||
|
||||
<p><strong>If the button doesn't work:</strong></p>
|
||||
<p>Copy and paste this link into your browser:</p>
|
||||
<div class="fallback-url">{{verificationUrl}}</div>
|
||||
|
||||
<p>Once verified, you'll have full access to your {{companyName}} account!</p>
|
||||
|
||||
<p>Questions? Reply to this email or contact us at {{supportEmail}}</p>
|
||||
|
||||
<p>If you didn't create this account, please ignore this email.</p>
|
||||
<hr>
|
||||
|
||||
<p>
|
||||
Best regards,<br>
|
||||
The {{companyName}} team
|
||||
</p>
|
||||
<p>Best regards,<br>The {{companyName}} team</p>
|
||||
|
||||
<div class="footer">
|
||||
<p>User ID: {{id}}</p>
|
||||
<p>© {{currentYear}} {{companyName}}. All rights reserved.</p>
|
||||
<p><small>This is an automated message. Please do not reply directly to this email.</small></p>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -155,7 +155,7 @@ class MailerService {
|
|||
|
||||
const info = await this.transporter.sendMail(mailOptions);
|
||||
|
||||
noFileLog.info({
|
||||
echo.debug({
|
||||
message: `Email sent successfully to ${options.to}`,
|
||||
messageId: info.messageId,
|
||||
subject: options.subject,
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import type { JWTConfig } from "#types/config";
|
||||
import type { validationResult } from "#types/lib";
|
||||
import type { simpleConfigValidation } from "#types/lib";
|
||||
|
||||
function isValidSecret(secret: string): boolean {
|
||||
if (!secret || secret.trim().length === 0) return false;
|
||||
|
@ -36,7 +36,7 @@ function isValidAlgorithm(algorithm: string): boolean {
|
|||
return supportedAlgorithms.includes(algorithm);
|
||||
}
|
||||
|
||||
function validateJWTConfig(config: JWTConfig): validationResult {
|
||||
function validateJWTConfig(config: JWTConfig): simpleConfigValidation {
|
||||
const errors: string[] = [];
|
||||
|
||||
if (!isValidSecret(config.secret)) {
|
||||
|
@ -67,8 +67,9 @@ function validateJWTConfig(config: JWTConfig): validationResult {
|
|||
}
|
||||
|
||||
return {
|
||||
valid: errors.length === 0,
|
||||
isValid: errors.length === 0,
|
||||
...(errors.length > 0 && { error: errors.join("; ") }),
|
||||
errors,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -2,11 +2,9 @@ import { isValidEmail } from "./email";
|
|||
import { isValidHostname, isValidPort } from "./general";
|
||||
|
||||
import type { MailerConfig } from "#types/config";
|
||||
import type { simpleConfigValidation } from "#types/lib";
|
||||
|
||||
function validateMailerConfig(config: MailerConfig): {
|
||||
isValid: boolean;
|
||||
errors: string[];
|
||||
} {
|
||||
function validateMailerConfig(config: MailerConfig): simpleConfigValidation {
|
||||
const errors: string[] = [];
|
||||
|
||||
const isValidSMTPAddress = isValidHostname(config.address);
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
import { echo } from "@atums/echo";
|
||||
import { redis } from "bun";
|
||||
import { environment } from "#environment/config";
|
||||
import { extraValues } from "#environment/extra";
|
||||
import { cassandra } from "#lib/database";
|
||||
import { mailerService } from "#lib/mailer";
|
||||
import { pika } from "#lib/utils";
|
||||
|
@ -8,8 +11,6 @@ import {
|
|||
isValidPassword,
|
||||
isValidUsername,
|
||||
} from "#lib/validation";
|
||||
|
||||
import { echo } from "@atums/echo";
|
||||
import type {
|
||||
ExtendedRequest,
|
||||
RegisterRequest,
|
||||
|
@ -116,6 +117,7 @@ async function handler(
|
|||
}
|
||||
|
||||
const userId = pika.gen("user");
|
||||
const verificationToken = Bun.randomUUIDv7();
|
||||
|
||||
const hashedPassword = await Bun.password.hash(password, {
|
||||
algorithm: "argon2id",
|
||||
|
@ -151,40 +153,81 @@ async function handler(
|
|||
createdAt: now.toISOString(),
|
||||
};
|
||||
|
||||
const response: RegisterResponse = {
|
||||
code: 201,
|
||||
success: true,
|
||||
message: "User registered successfully",
|
||||
user: responseUser,
|
||||
};
|
||||
|
||||
try {
|
||||
await redis.set(
|
||||
`mail-verification:${verificationToken}`,
|
||||
JSON.stringify({
|
||||
userId: responseUser.id,
|
||||
email: responseUser.email,
|
||||
}),
|
||||
"EX",
|
||||
3 * 60 * 60, // 3h
|
||||
);
|
||||
|
||||
const emailVariables = {
|
||||
subject: "Welcome to void",
|
||||
companyName: "void",
|
||||
subject: `Welcome to ${extraValues.companyName}`,
|
||||
companyName: extraValues.companyName,
|
||||
id: responseUser.id,
|
||||
displayName: responseUser.displayName || responseUser.username,
|
||||
isVerified: "Pending verification",
|
||||
verificationUrl: `${environment.fqdn}/verify?token=generated_token`, // TODO: Actually generate a token
|
||||
willExpire: "This link will expire in 3 hours",
|
||||
verificationUrl: `${environment.frontendFqdn}/user/verify?token=${verificationToken}`,
|
||||
supportEmail: extraValues.supportEmail,
|
||||
currentYear: new Date().getFullYear(),
|
||||
};
|
||||
|
||||
mailerService.sendTemplateEmail(
|
||||
await mailerService.sendTemplateEmail(
|
||||
responseUser.email,
|
||||
"Welcome to void",
|
||||
`Welcome to ${extraValues.companyName}`,
|
||||
"register",
|
||||
emailVariables,
|
||||
);
|
||||
|
||||
const response: RegisterResponse = {
|
||||
code: 201,
|
||||
success: true,
|
||||
message:
|
||||
"User registered successfully - please check your email to verify your account",
|
||||
user: responseUser,
|
||||
};
|
||||
|
||||
return Response.json(response, { status: 201 });
|
||||
} catch (error) {
|
||||
try {
|
||||
await cassandra.execute("DELETE FROM users WHERE id = ?", [userId]);
|
||||
|
||||
await redis.del(`mail-verification:${verificationToken}`);
|
||||
} catch (cleanupError) {
|
||||
echo.error({
|
||||
message: "Failed to cleanup user after email failure",
|
||||
error: cleanupError,
|
||||
userId,
|
||||
email: responseUser.email,
|
||||
});
|
||||
}
|
||||
|
||||
echo.error({
|
||||
message: "Failed to send registration email",
|
||||
message: "Registration failed - could not send verification email",
|
||||
error,
|
||||
to: responseUser.email,
|
||||
userId,
|
||||
email: responseUser.email,
|
||||
template: "register",
|
||||
});
|
||||
}
|
||||
|
||||
return Response.json(response, { status: 201 });
|
||||
} catch {
|
||||
const response: RegisterResponse = {
|
||||
code: 500,
|
||||
success: false,
|
||||
error:
|
||||
"Registration failed - unable to send verification email. Please try again.",
|
||||
};
|
||||
return Response.json(response, { status: 500 });
|
||||
}
|
||||
} catch (error) {
|
||||
echo.error({
|
||||
message: "Registration failed with unexpected error",
|
||||
error,
|
||||
});
|
||||
|
||||
const response: RegisterResponse = {
|
||||
code: 500,
|
||||
success: false,
|
||||
|
|
246
src/routes/user/verify.ts
Normal file
246
src/routes/user/verify.ts
Normal file
|
@ -0,0 +1,246 @@
|
|||
import { echo } from "@atums/echo";
|
||||
import { redis } from "bun";
|
||||
import { sessionManager } from "#lib/auth";
|
||||
import { cassandra } from "#lib/database";
|
||||
|
||||
import type {
|
||||
ExtendedRequest,
|
||||
RouteDef,
|
||||
UserResponse,
|
||||
VerificationData,
|
||||
VerifyEmailResponse,
|
||||
} from "#types/server";
|
||||
|
||||
const routeDef: RouteDef = {
|
||||
method: "GET",
|
||||
accepts: "*/*",
|
||||
returns: "application/json",
|
||||
};
|
||||
|
||||
async function handler(request: ExtendedRequest): Promise<Response> {
|
||||
try {
|
||||
const { token } = request.query;
|
||||
|
||||
if (!token || typeof token !== "string" || token.trim() === "") {
|
||||
const response: VerifyEmailResponse = {
|
||||
code: 400,
|
||||
success: false,
|
||||
error: "Verification token is required",
|
||||
};
|
||||
return Response.json(response, { status: 400 });
|
||||
}
|
||||
|
||||
const verificationKey = `mail-verification:${token}`;
|
||||
const verificationDataRaw = await redis.get(verificationKey);
|
||||
|
||||
if (!verificationDataRaw) {
|
||||
const response: VerifyEmailResponse = {
|
||||
code: 400,
|
||||
success: false,
|
||||
error: "Invalid or expired verification token",
|
||||
};
|
||||
return Response.json(response, { status: 400 });
|
||||
}
|
||||
|
||||
let verificationData: VerificationData;
|
||||
try {
|
||||
verificationData = JSON.parse(verificationDataRaw);
|
||||
} catch {
|
||||
await redis.del(verificationKey);
|
||||
|
||||
const response: VerifyEmailResponse = {
|
||||
code: 400,
|
||||
success: false,
|
||||
error: "Invalid verification token format",
|
||||
};
|
||||
return Response.json(response, { status: 400 });
|
||||
}
|
||||
|
||||
if (!verificationData.userId || !verificationData.email) {
|
||||
await redis.del(verificationKey);
|
||||
|
||||
const response: VerifyEmailResponse = {
|
||||
code: 400,
|
||||
success: false,
|
||||
error: "Invalid verification data",
|
||||
};
|
||||
return Response.json(response, { status: 400 });
|
||||
}
|
||||
|
||||
const userQuery = `
|
||||
SELECT id, username, display_name, email, is_verified, created_at, updated_at
|
||||
FROM users WHERE id = ? LIMIT 1
|
||||
`;
|
||||
|
||||
const userResult = (await cassandra.execute(userQuery, [
|
||||
verificationData.userId,
|
||||
])) as {
|
||||
rows: Array<{
|
||||
id: string;
|
||||
username: string;
|
||||
display_name: string | null;
|
||||
email: string;
|
||||
is_verified: boolean;
|
||||
created_at: Date;
|
||||
updated_at: Date;
|
||||
}>;
|
||||
};
|
||||
|
||||
if (!userResult?.rows || userResult.rows.length === 0) {
|
||||
await redis.del(verificationKey);
|
||||
|
||||
const response: VerifyEmailResponse = {
|
||||
code: 404,
|
||||
success: false,
|
||||
error: "User not found",
|
||||
};
|
||||
return Response.json(response, { status: 404 });
|
||||
}
|
||||
|
||||
const user = userResult.rows[0];
|
||||
if (!user) {
|
||||
await redis.del(verificationKey);
|
||||
|
||||
const response: VerifyEmailResponse = {
|
||||
code: 404,
|
||||
success: false,
|
||||
error: "User not found",
|
||||
};
|
||||
return Response.json(response, { status: 404 });
|
||||
}
|
||||
|
||||
if (user.email !== verificationData.email) {
|
||||
await redis.del(verificationKey);
|
||||
|
||||
const response: VerifyEmailResponse = {
|
||||
code: 400,
|
||||
success: false,
|
||||
error: "Verification token does not match current email address",
|
||||
};
|
||||
return Response.json(response, { status: 400 });
|
||||
}
|
||||
|
||||
if (user.is_verified) {
|
||||
await redis.del(verificationKey);
|
||||
|
||||
const responseUser: UserResponse = {
|
||||
id: user.id,
|
||||
username: user.username,
|
||||
displayName: user.display_name,
|
||||
email: user.email,
|
||||
isVerified: user.is_verified,
|
||||
createdAt: user.created_at.toISOString(),
|
||||
};
|
||||
|
||||
const response: VerifyEmailResponse = {
|
||||
code: 200,
|
||||
success: true,
|
||||
message: "Email is already verified",
|
||||
user: responseUser,
|
||||
};
|
||||
return Response.json(response, { status: 200 });
|
||||
}
|
||||
|
||||
const updateQuery = `
|
||||
UPDATE users
|
||||
SET is_verified = ?, updated_at = ?
|
||||
WHERE id = ?
|
||||
`;
|
||||
|
||||
await cassandra.execute(updateQuery, [true, new Date(), user.id]);
|
||||
|
||||
await redis.del(verificationKey);
|
||||
|
||||
const updatedUserResult = (await cassandra.execute(userQuery, [
|
||||
user.id,
|
||||
])) as {
|
||||
rows: Array<{
|
||||
id: string;
|
||||
username: string;
|
||||
display_name: string | null;
|
||||
email: string;
|
||||
is_verified: boolean;
|
||||
created_at: Date;
|
||||
updated_at: Date;
|
||||
}>;
|
||||
};
|
||||
|
||||
const updatedUser = updatedUserResult.rows[0];
|
||||
if (!updatedUser) {
|
||||
const response: VerifyEmailResponse = {
|
||||
code: 500,
|
||||
success: false,
|
||||
error: "Failed to fetch updated user data",
|
||||
};
|
||||
return Response.json(response, { status: 500 });
|
||||
}
|
||||
|
||||
const session = await sessionManager.getSession(request);
|
||||
let sessionCookie: string | undefined;
|
||||
|
||||
if (session && session.id === user.id) {
|
||||
try {
|
||||
const userAgent = request.headers.get("User-Agent") || "Unknown";
|
||||
const updatedSessionPayload = {
|
||||
id: updatedUser.id,
|
||||
username: updatedUser.username,
|
||||
email: updatedUser.email,
|
||||
isVerified: updatedUser.is_verified,
|
||||
displayName: updatedUser.display_name,
|
||||
createdAt: updatedUser.created_at.toISOString(),
|
||||
updatedAt: updatedUser.updated_at.toISOString(),
|
||||
};
|
||||
|
||||
sessionCookie = await sessionManager.updateSession(
|
||||
request,
|
||||
updatedSessionPayload,
|
||||
userAgent,
|
||||
);
|
||||
} catch (sessionError) {
|
||||
echo.warn({
|
||||
message: "Failed to update session after email verification",
|
||||
error: sessionError,
|
||||
userId: user.id,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const responseUser: UserResponse = {
|
||||
id: updatedUser.id,
|
||||
username: updatedUser.username,
|
||||
displayName: updatedUser.display_name,
|
||||
email: updatedUser.email,
|
||||
isVerified: updatedUser.is_verified,
|
||||
createdAt: updatedUser.created_at.toISOString(),
|
||||
};
|
||||
|
||||
const response: VerifyEmailResponse = {
|
||||
code: 200,
|
||||
success: true,
|
||||
message: "Email verified successfully",
|
||||
user: responseUser,
|
||||
};
|
||||
|
||||
return Response.json(response, {
|
||||
status: 200,
|
||||
headers: {
|
||||
"Set-Cookie": sessionCookie ? sessionCookie : "",
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
echo.error({
|
||||
message: "Error during email verification",
|
||||
error,
|
||||
token: request.query.token,
|
||||
});
|
||||
|
||||
const response: VerifyEmailResponse = {
|
||||
code: 500,
|
||||
success: false,
|
||||
error: "Internal server error",
|
||||
};
|
||||
return Response.json(response, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
export { handler, routeDef };
|
|
@ -2,7 +2,8 @@ type Environment = {
|
|||
port: number;
|
||||
host: string;
|
||||
development: boolean;
|
||||
fqdn: string;
|
||||
frontendFqdn: string;
|
||||
backendFqdn: string;
|
||||
};
|
||||
|
||||
export type { Environment };
|
||||
|
|
|
@ -25,9 +25,15 @@ interface UrlValidationResult extends validationResult {
|
|||
normalizedUrl?: string;
|
||||
}
|
||||
|
||||
type simpleConfigValidation = {
|
||||
isValid: boolean;
|
||||
errors: string[];
|
||||
};
|
||||
|
||||
export type {
|
||||
genericValidation,
|
||||
validationResult,
|
||||
UrlValidationOptions,
|
||||
UrlValidationResult,
|
||||
simpleConfigValidation,
|
||||
};
|
||||
|
|
|
@ -2,5 +2,6 @@ export * from "./base";
|
|||
export * from "./responses";
|
||||
export * from "./register";
|
||||
export * from "./login";
|
||||
export * from "./verify";
|
||||
|
||||
export * from "./update";
|
||||
|
|
|
@ -1,7 +0,0 @@
|
|||
interface UpdatePasswordRequest {
|
||||
currentPassword: string;
|
||||
newPassword: string;
|
||||
logoutAllSessions?: boolean; // defaults to false
|
||||
}
|
||||
|
||||
export type { UpdatePasswordRequest };
|
13
types/server/requests/user/verify.ts
Normal file
13
types/server/requests/user/verify.ts
Normal file
|
@ -0,0 +1,13 @@
|
|||
import type { BaseResponse } from "../base";
|
||||
import type { UserResponse } from "./base";
|
||||
|
||||
interface VerifyEmailResponse extends BaseResponse {
|
||||
user?: UserResponse;
|
||||
}
|
||||
|
||||
interface VerificationData {
|
||||
userId: string;
|
||||
email: string;
|
||||
}
|
||||
|
||||
export type { VerifyEmailResponse, VerificationData };
|
Loading…
Add table
Add a link
Reference in a new issue