fix: env cleanup, updates for new apps
This commit is contained in:
10
.env.example
10
.env.example
@@ -11,8 +11,6 @@
|
||||
# variable below. Do not leave production values blank.
|
||||
# ──────────────────────────────────────────────────────────────────────────
|
||||
|
||||
NODE_ENV="development"
|
||||
|
||||
# ── Frontend / public (safe to expose to the browser, VITE_* is shipped) ──
|
||||
VITE_DOMAIN="http://localhost:3000"
|
||||
VITE_AWS_BUCKET_STRING="https://example-bucket.s3.amazonaws.com/"
|
||||
@@ -20,10 +18,6 @@ VITE_DOWNLOAD_BUCKET_STRING="example-downloads-bucket"
|
||||
VITE_GOOGLE_CLIENT_ID="<google-oauth-client-id>.apps.googleusercontent.com"
|
||||
VITE_GOOGLE_CLIENT_ID_DEV="<google-oauth-client-id-dev>.apps.googleusercontent.com"
|
||||
VITE_GOOGLE_CLIENT_ID_MAGIC_DELVE="<google-oauth-client-id-magicdelve>.apps.googleusercontent.com"
|
||||
# Server-side Google client ID for verifying Google ID tokens from the Nessa
|
||||
# iOS app via verifyIdToken({ audience }). MUST match the iOS app's
|
||||
# GID_CLIENT_ID in Nessa/Resources/GoogleSignIn.xcconfig.
|
||||
GOOGLE_CLIENT_ID="<google-oauth-client-id-ios>.apps.googleusercontent.com"
|
||||
VITE_GITHUB_CLIENT_ID="<github-oauth-client-id>"
|
||||
VITE_GITHUB_CLIENT_ID_DEV="<github-oauth-client-id-dev>"
|
||||
VITE_INFILL_ENDPOINT="https://infill.example.com/infill"
|
||||
@@ -52,7 +46,6 @@ GOOGLE_CLIENT_SECRET="<rotate-in-google-cloud-console>" # GOCSPX-...
|
||||
GOOGLE_CLIENT_SECRET_DEV="<rotate-in-google-cloud-console>"
|
||||
GITHUB_CLIENT_SECRET="<rotate-in-github-oauth-apps>"
|
||||
GITHUB_CLIENT_SECRET_DEV="<rotate-in-github-oauth-apps>"
|
||||
APPLE_SHARED_SECRET="<rotate-in-app-developer-portal>" # App Store Server Notifications
|
||||
|
||||
# ── Cloudflare Turnstile ──
|
||||
TURNSTILE_SECRET_KEY="<rotate-in-cloudflare-dashboard>" # 0x...
|
||||
@@ -65,6 +58,9 @@ TURSO_LINEAGE_URL="libsql://<lineage-db>.turso.io"
|
||||
TURSO_LINEAGE_TOKEN="<rotate-in-turso-dashboard>"
|
||||
NESSA_DB_URL="libsql://<nessa-db>.turso.io"
|
||||
NESSA_DB_TOKEN="<rotate-in-turso-dashboard>"
|
||||
NESSA_GOOGLE_CLIENT_ID="<google-oauth-client-id-ios>.apps.googleusercontent.com"
|
||||
|
||||
APPLE_CLIENT_ID="<services-id-for-nessa>"
|
||||
|
||||
# ── Infra / integration tokens ──
|
||||
INFILL_BEARER_TOKEN="<rotate-at-infill-service>"
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -9,6 +9,7 @@ app.config.timestamp_*.js
|
||||
# Environment
|
||||
.env
|
||||
.env*.local
|
||||
.env.bak
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
|
||||
5
src/env/server.ts
vendored
5
src/env/server.ts
vendored
@@ -57,10 +57,9 @@ const serverEnvSchema = z.object({
|
||||
NESSA_DB_URL: z.string().min(1),
|
||||
NESSA_DB_TOKEN: z.string().min(1),
|
||||
NESSA_JWT_SECRET: z.string().min(1),
|
||||
// p8-005: dedicated Lineage game JWT signing secret, isolated from the
|
||||
// web JWT_SECRET_KEY so a web admin secret cannot mint Lineage tokens.
|
||||
LINEAGE_JWT_SECRET: z.string().min(32),
|
||||
APPLE_CLIENT_ID: z.string().min(1).optional(),
|
||||
APPLE_CLIENT_ID_NESSA: z.string().min(1).optional(),
|
||||
APPLE_CLIENT_ID_LINEAGE: z.string().min(1).optional(),
|
||||
VITE_TURNSTILE_SITE_KEY: z.string().min(1),
|
||||
TURNSTILE_SECRET_KEY: z.string().min(1)
|
||||
});
|
||||
|
||||
@@ -426,8 +426,8 @@ export const lineageAuthRouter = createTRPCRouter({
|
||||
algorithms: ["RS256"],
|
||||
issuer: "https://appleid.apple.com"
|
||||
};
|
||||
if (env.APPLE_CLIENT_ID) {
|
||||
jwtOptions.audience = env.APPLE_CLIENT_ID;
|
||||
if (env.APPLE_CLIENT_ID_LINEAGE) {
|
||||
jwtOptions.audience = env.APPLE_CLIENT_ID_LINEAGE;
|
||||
}
|
||||
const { payload: tokenPayload } = await jwtVerify(
|
||||
input.idToken,
|
||||
|
||||
@@ -26,7 +26,10 @@ export async function assertWorkoutOwned(
|
||||
throw new TRPCError({ code: "NOT_FOUND", message: "Workout not found" });
|
||||
}
|
||||
if ((row.rows[0] as any).userId !== userId) {
|
||||
throw new TRPCError({ code: "FORBIDDEN", message: "Not the workout owner" });
|
||||
throw new TRPCError({
|
||||
code: "FORBIDDEN",
|
||||
message: "Not the workout owner"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,10 +44,16 @@ export async function assertAuthProviderOwned(
|
||||
args: [providerId]
|
||||
});
|
||||
if (row.rows.length === 0) {
|
||||
throw new TRPCError({ code: "NOT_FOUND", message: "Auth provider not found" });
|
||||
throw new TRPCError({
|
||||
code: "NOT_FOUND",
|
||||
message: "Auth provider not found"
|
||||
});
|
||||
}
|
||||
if ((row.rows[0] as any).userId !== userId) {
|
||||
throw new TRPCError({ code: "FORBIDDEN", message: "Not the auth provider owner" });
|
||||
throw new TRPCError({
|
||||
code: "FORBIDDEN",
|
||||
message: "Not the auth provider owner"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,7 +71,10 @@ export async function assertExerciseLibraryOwned(
|
||||
throw new TRPCError({ code: "NOT_FOUND", message: "Exercise not found" });
|
||||
}
|
||||
if ((row.rows[0] as any).userId !== userId) {
|
||||
throw new TRPCError({ code: "FORBIDDEN", message: "Not the exercise owner" });
|
||||
throw new TRPCError({
|
||||
code: "FORBIDDEN",
|
||||
message: "Not the exercise owner"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -708,8 +720,8 @@ export const nessaDbRouter = createTRPCRouter({
|
||||
algorithms: ["RS256"],
|
||||
issuer: "https://appleid.apple.com"
|
||||
};
|
||||
if (env.APPLE_CLIENT_ID) {
|
||||
jwtOptions.audience = env.APPLE_CLIENT_ID;
|
||||
if (env.APPLE_CLIENT_ID_NESSA) {
|
||||
jwtOptions.audience = env.APPLE_CLIENT_ID_NESSA;
|
||||
}
|
||||
const { payload: tokenPayload } = await jwtVerify(
|
||||
input.idToken,
|
||||
@@ -2005,9 +2017,16 @@ export const nessaDbRouter = createTRPCRouter({
|
||||
args: [input.id]
|
||||
});
|
||||
if (sample.rows.length === 0) {
|
||||
throw new TRPCError({ code: "NOT_FOUND", message: "Heart rate sample not found" });
|
||||
throw new TRPCError({
|
||||
code: "NOT_FOUND",
|
||||
message: "Heart rate sample not found"
|
||||
});
|
||||
}
|
||||
await assertWorkoutOwned(conn, (sample.rows[0] as any).workoutId, ctx.nessaUserId);
|
||||
await assertWorkoutOwned(
|
||||
conn,
|
||||
(sample.rows[0] as any).workoutId,
|
||||
ctx.nessaUserId
|
||||
);
|
||||
await conn.execute({
|
||||
sql: `UPDATE heartRateSamples SET timestamp = ?, bpm = ?, source = ? WHERE id = ?`,
|
||||
args: [input.timestamp, input.bpm, input.source ?? null, input.id]
|
||||
@@ -2032,9 +2051,16 @@ export const nessaDbRouter = createTRPCRouter({
|
||||
args: [input.id]
|
||||
});
|
||||
if (sample.rows.length === 0) {
|
||||
throw new TRPCError({ code: "NOT_FOUND", message: "Heart rate sample not found" });
|
||||
throw new TRPCError({
|
||||
code: "NOT_FOUND",
|
||||
message: "Heart rate sample not found"
|
||||
});
|
||||
}
|
||||
await assertWorkoutOwned(conn, (sample.rows[0] as any).workoutId, ctx.nessaUserId);
|
||||
await assertWorkoutOwned(
|
||||
conn,
|
||||
(sample.rows[0] as any).workoutId,
|
||||
ctx.nessaUserId
|
||||
);
|
||||
await conn.execute({
|
||||
sql: "DELETE FROM heartRateSamples WHERE id = ?",
|
||||
args: [input.id]
|
||||
@@ -2091,9 +2117,16 @@ export const nessaDbRouter = createTRPCRouter({
|
||||
args: [input.id]
|
||||
});
|
||||
if (sample.rows.length === 0) {
|
||||
throw new TRPCError({ code: "NOT_FOUND", message: "Location sample not found" });
|
||||
throw new TRPCError({
|
||||
code: "NOT_FOUND",
|
||||
message: "Location sample not found"
|
||||
});
|
||||
}
|
||||
await assertWorkoutOwned(conn, (sample.rows[0] as any).workoutId, ctx.nessaUserId);
|
||||
await assertWorkoutOwned(
|
||||
conn,
|
||||
(sample.rows[0] as any).workoutId,
|
||||
ctx.nessaUserId
|
||||
);
|
||||
await conn.execute({
|
||||
sql: `UPDATE locationSamples SET timestamp = ?, latitude = ?, longitude = ?, altitude = ?, horizontalAccuracy = ?, verticalAccuracy = ?, speed = ?, course = ? WHERE id = ?`,
|
||||
args: [
|
||||
@@ -2128,9 +2161,16 @@ export const nessaDbRouter = createTRPCRouter({
|
||||
args: [input.id]
|
||||
});
|
||||
if (sample.rows.length === 0) {
|
||||
throw new TRPCError({ code: "NOT_FOUND", message: "Location sample not found" });
|
||||
throw new TRPCError({
|
||||
code: "NOT_FOUND",
|
||||
message: "Location sample not found"
|
||||
});
|
||||
}
|
||||
await assertWorkoutOwned(conn, (sample.rows[0] as any).workoutId, ctx.nessaUserId);
|
||||
await assertWorkoutOwned(
|
||||
conn,
|
||||
(sample.rows[0] as any).workoutId,
|
||||
ctx.nessaUserId
|
||||
);
|
||||
await conn.execute({
|
||||
sql: "DELETE FROM locationSamples WHERE id = ?",
|
||||
args: [input.id]
|
||||
@@ -2188,9 +2228,16 @@ export const nessaDbRouter = createTRPCRouter({
|
||||
args: [input.id]
|
||||
});
|
||||
if (split.rows.length === 0) {
|
||||
throw new TRPCError({ code: "NOT_FOUND", message: "Workout split not found" });
|
||||
throw new TRPCError({
|
||||
code: "NOT_FOUND",
|
||||
message: "Workout split not found"
|
||||
});
|
||||
}
|
||||
await assertWorkoutOwned(conn, (split.rows[0] as any).workoutId, ctx.nessaUserId);
|
||||
await assertWorkoutOwned(
|
||||
conn,
|
||||
(split.rows[0] as any).workoutId,
|
||||
ctx.nessaUserId
|
||||
);
|
||||
await conn.execute({
|
||||
sql: `UPDATE workoutSplits SET splitNumber = ?, distanceMeters = ?, durationSeconds = ?, startTimestamp = ?, endTimestamp = ?, averageHeartRate = ?, averagePace = ?, elevationGain = ?, elevationLoss = ? WHERE id = ?`,
|
||||
args: [
|
||||
@@ -2226,9 +2273,16 @@ export const nessaDbRouter = createTRPCRouter({
|
||||
args: [input.id]
|
||||
});
|
||||
if (split.rows.length === 0) {
|
||||
throw new TRPCError({ code: "NOT_FOUND", message: "Workout split not found" });
|
||||
throw new TRPCError({
|
||||
code: "NOT_FOUND",
|
||||
message: "Workout split not found"
|
||||
});
|
||||
}
|
||||
await assertWorkoutOwned(conn, (split.rows[0] as any).workoutId, ctx.nessaUserId);
|
||||
await assertWorkoutOwned(
|
||||
conn,
|
||||
(split.rows[0] as any).workoutId,
|
||||
ctx.nessaUserId
|
||||
);
|
||||
await conn.execute({
|
||||
sql: "DELETE FROM workoutSplits WHERE id = ?",
|
||||
args: [input.id]
|
||||
|
||||
Reference in New Issue
Block a user