summaryrefslogtreecommitdiff
path: root/src/routes/parent.ts
diff options
context:
space:
mode:
authorJustZvan <justzvan@justzvan.xyz>2026-04-08 19:22:29 +0200
committerJustZvan <justzvan@justzvan.xyz>2026-04-08 19:22:29 +0200
commit12a1e744f90fe7f39808caa3f97d6885a883f34a (patch)
tree67f5b9c0bd72858dccf2d06b1b4bc7ba6ad417e0 /src/routes/parent.ts
parent6a5a165a035459af601006286e4a6e46ded1487c (diff)
feat: use otp for kid linkmain
Diffstat (limited to 'src/routes/parent.ts')
-rw-r--r--src/routes/parent.ts79
1 files changed, 78 insertions, 1 deletions
diff --git a/src/routes/parent.ts b/src/routes/parent.ts
index d800dbf..41fb85c 100644
--- a/src/routes/parent.ts
+++ b/src/routes/parent.ts
@@ -1,11 +1,17 @@
import express from "express";
import { authParent } from "../middleware/auth";
import { db } from "../db/db";
-import { deviceConfig, linkedDevices, users, alerts, galleryScanningMode } from "../db/schema";
+import { deviceConfig, linkedDevices, users, alerts } from "../db/schema";
import { eq, and, desc } from "drizzle-orm";
import { isValidPushToken } from "../notifications/push";
import { logger } from "../lib/pino";
import { z } from "zod";
+import { redis } from "../db/redis/client";
+import {
+ generateKidLinkCode,
+ getKidLinkCodeRedisKey,
+ KID_LINK_CODE_TTL_SECONDS,
+} from "../account/kid_link_code";
/** Validates email verification code from user input */
const VerifyEmailSchema = z.object({
@@ -172,6 +178,77 @@ function createParentRouter(
}
});
+ router.post("/parent/kid-link-code", authParent, async (req, res) => {
+ const parentId = req.user!.id;
+
+ try {
+ const parent = await db
+ .select({ emailVerified: users.emailVerified })
+ .from(users)
+ .where(eq(users.id, parentId))
+ .limit(1);
+
+ if (parent.length === 0) {
+ logger.warn({ parentId }, "User not found for kid link code request");
+ return res
+ .status(404)
+ .json({ success: false, reason: "User not found" });
+ }
+
+ if (!parent[0]!.emailVerified) {
+ logger.warn(
+ { parentId },
+ "Kid link code request rejected: parent email not verified",
+ );
+ return res.status(400).json({
+ success: false,
+ reason: "Verify your email in the parent app before linking devices",
+ });
+ }
+
+ for (let attempt = 0; attempt < 5; attempt++) {
+ const code = generateKidLinkCode();
+ const redisKey = getKidLinkCodeRedisKey(code);
+
+ const stored = await redis.set(
+ redisKey,
+ parentId.toString(),
+ "EX",
+ KID_LINK_CODE_TTL_SECONDS,
+ "NX",
+ );
+
+ if (stored === "OK") {
+ logger.info(
+ { parentId },
+ "Generated one-time kid link code successfully",
+ );
+
+ return res.json({
+ success: true,
+ code,
+ expiresInSeconds: KID_LINK_CODE_TTL_SECONDS,
+ });
+ }
+ }
+
+ logger.warn(
+ { parentId },
+ "Failed to allocate unique kid link code after retries",
+ );
+ return res.status(503).json({
+ success: false,
+ reason: "Failed to generate link code, please try again",
+ });
+ } catch (e) {
+ logger.error({ error: e, parentId }, "Failed to generate kid link code");
+ return res.status(500).json({
+ success: false,
+ reason: "Failed to generate kid link code",
+ });
+ }
+ });
+
router.get("/parent/devices", authParent, async (req, res) => {
const parentId = req.user!.id;