forked from Bee-Balanced/Server-Repo
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathverification.js
More file actions
121 lines (101 loc) · 3.1 KB
/
verification.js
File metadata and controls
121 lines (101 loc) · 3.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
import crypto from "crypto";
import dotenv from "dotenv";
import { Knock } from "@knocklabs/node";
import db from "./db.js";
dotenv.config();
const DEFAULT_TOKEN_TTL_HOURS = 24;
let verificationColumnsReady = false;
let verificationColumnsPromise = null;
function getEnvValue(name) {
const value = process.env[name];
if (typeof value !== "string") return "";
return value.trim();
}
function getTokenTtlHours() {
const configured = Number(process.env.EMAIL_VERIFICATION_TOKEN_TTL_HOURS);
if (!Number.isNaN(configured) && configured > 0) {
return configured;
}
return DEFAULT_TOKEN_TTL_HOURS;
}
export function createEmailVerificationToken() {
return crypto.randomBytes(32).toString("hex");
}
export function getEmailVerificationExpiryDate() {
const expiresAt = new Date();
expiresAt.setHours(expiresAt.getHours() + getTokenTtlHours());
return expiresAt;
}
export function getAppBaseUrl(req) {
if (process.env.APP_BASE_URL) {
return process.env.APP_BASE_URL.replace(/\/+$/, "");
}
if (req) {
return `${req.protocol}://${req.get("host")}`;
}
return "http://localhost:8000";
}
export async function ensureEmailVerificationColumns() {
if (verificationColumnsReady) return;
if (verificationColumnsPromise) return verificationColumnsPromise;
const requiredColumns = [
{
name: "email_verified",
sql: "ADD COLUMN email_verified TINYINT(1) NOT NULL DEFAULT 0",
},
{
name: "email_verification_token",
sql: "ADD COLUMN email_verification_token VARCHAR(128) NULL",
},
{
name: "email_verification_expires_at",
sql: "ADD COLUMN email_verification_expires_at DATETIME NULL",
},
];
verificationColumnsPromise = (async () => {
for (const column of requiredColumns) {
const [rows] = await db.query("SHOW COLUMNS FROM users LIKE ?", [column.name]);
if (!rows.length) {
await db.query(`ALTER TABLE users ${column.sql}`);
}
}
verificationColumnsReady = true;
})();
try {
await verificationColumnsPromise;
} catch (err) {
verificationColumnsPromise = null;
throw err;
}
}
export async function sendVerificationEmail({ email, name, token, req }) {
const apiKey = getEnvValue("KNOCK_API_KEY");
const workflowKey = getEnvValue("KNOCK_VERIFICATION_WORKFLOW_KEY");
if (!apiKey || !workflowKey) {
const missing = [];
if (!apiKey) missing.push("KNOCK_API_KEY");
if (!workflowKey) missing.push("KNOCK_VERIFICATION_WORKFLOW_KEY");
throw new Error(`Knock email verification is not configured. Missing: ${missing.join(", ")}`);
}
const knock = new Knock(apiKey);
const verificationUrl = `${getAppBaseUrl(req)}/verify-email?token=${encodeURIComponent(token)}`;
const workflowRun = await knock.workflows.trigger(workflowKey, {
recipients: [
{
id: email,
email,
name,
},
],
data: {
user_name: name,
verification_url: verificationUrl,
},
});
console.log("Knock verification workflow triggered:", {
workflowKey,
recipientEmail: email,
workflowRunId: workflowRun.workflow_run_id,
});
return workflowRun;
}