🪻 distributed transcription service thistle.dunkirk.sh
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: update transcription auth to work with classes

+70 -13
+70 -13
src/index.ts
··· 46 46 toggleClassArchive, 47 47 updateMeetingTime, 48 48 } from "./lib/classes"; 49 - import { handleError, ValidationErrors } from "./lib/errors"; 49 + import { AuthErrors, handleError, ValidationErrors } from "./lib/errors"; 50 50 import { 51 + hasActiveSubscription, 51 52 requireAdmin, 52 53 requireAuth, 53 54 requireSubscription, ··· 977 978 "/api/transcriptions/:id/stream": { 978 979 GET: async (req) => { 979 980 try { 980 - const user = requireSubscription(req); 981 + const user = requireAuth(req); 981 982 const transcriptionId = req.params.id; 982 983 // Verify ownership 983 984 const transcription = db 984 - .query<{ id: string; user_id: number; status: string }, [string]>( 985 - "SELECT id, user_id, status FROM transcriptions WHERE id = ?", 985 + .query<{ id: string; user_id: number; class_id: string | null; status: string }, [string]>( 986 + "SELECT id, user_id, class_id, status FROM transcriptions WHERE id = ?", 986 987 ) 987 988 .get(transcriptionId); 988 - if (!transcription || transcription.user_id !== user.id) { 989 + 990 + if (!transcription) { 989 991 return Response.json( 990 992 { error: "Transcription not found" }, 991 993 { status: 404 }, 992 994 ); 995 + } 996 + 997 + // Check access permissions 998 + const isOwner = transcription.user_id === user.id; 999 + const isAdmin = user.role === "admin"; 1000 + let isClassMember = false; 1001 + 1002 + // If transcription belongs to a class, check enrollment 1003 + if (transcription.class_id) { 1004 + isClassMember = isUserEnrolledInClass(user.id, transcription.class_id); 1005 + } 1006 + 1007 + // Allow access if: owner, admin, or enrolled in the class 1008 + if (!isOwner && !isAdmin && !isClassMember) { 1009 + return Response.json( 1010 + { error: "Transcription not found" }, 1011 + { status: 404 }, 1012 + ); 1013 + } 1014 + 1015 + // Require subscription only if accessing own transcription (not class) 1016 + if (isOwner && !transcription.class_id && !isAdmin && !hasActiveSubscription(user.id)) { 1017 + throw AuthErrors.subscriptionRequired(); 993 1018 } 994 1019 // Event-driven SSE stream with reconnection support 995 1020 const stream = new ReadableStream({ ··· 1107 1132 "/api/transcriptions/:id": { 1108 1133 GET: async (req) => { 1109 1134 try { 1110 - const user = requireSubscription(req); 1135 + const user = requireAuth(req); 1111 1136 const transcriptionId = req.params.id; 1112 1137 1113 1138 // Verify ownership or admin ··· 1116 1141 { 1117 1142 id: string; 1118 1143 user_id: number; 1144 + class_id: string | null; 1119 1145 filename: string; 1120 1146 original_filename: string; 1121 1147 status: string; ··· 1124 1150 }, 1125 1151 [string] 1126 1152 >( 1127 - "SELECT id, user_id, filename, original_filename, status, progress, created_at FROM transcriptions WHERE id = ?", 1153 + "SELECT id, user_id, class_id, filename, original_filename, status, progress, created_at FROM transcriptions WHERE id = ?", 1128 1154 ) 1129 1155 .get(transcriptionId); 1130 1156 ··· 1135 1161 ); 1136 1162 } 1137 1163 1138 - // Allow access if user owns it or is admin 1139 - if (transcription.user_id !== user.id && user.role !== "admin") { 1164 + // Check access permissions 1165 + const isOwner = transcription.user_id === user.id; 1166 + const isAdmin = user.role === "admin"; 1167 + let isClassMember = false; 1168 + 1169 + // If transcription belongs to a class, check enrollment 1170 + if (transcription.class_id) { 1171 + isClassMember = isUserEnrolledInClass(user.id, transcription.class_id); 1172 + } 1173 + 1174 + // Allow access if: owner, admin, or enrolled in the class 1175 + if (!isOwner && !isAdmin && !isClassMember) { 1140 1176 return Response.json( 1141 1177 { error: "Transcription not found" }, 1142 1178 { status: 404 }, 1143 1179 ); 1180 + } 1181 + 1182 + // Require subscription only if accessing own transcription (not class) 1183 + if (isOwner && !transcription.class_id && !isAdmin && !hasActiveSubscription(user.id)) { 1184 + throw AuthErrors.subscriptionRequired(); 1144 1185 } 1145 1186 1146 1187 if (transcription.status !== "completed") { ··· 1194 1235 "/api/transcriptions/:id/audio": { 1195 1236 GET: async (req) => { 1196 1237 try { 1197 - const user = requireSubscription(req); 1238 + const user = requireAuth(req); 1198 1239 const transcriptionId = req.params.id; 1199 1240 1200 1241 // Verify ownership or admin ··· 1203 1244 { 1204 1245 id: string; 1205 1246 user_id: number; 1247 + class_id: string | null; 1206 1248 filename: string; 1207 1249 status: string; 1208 1250 }, 1209 1251 [string] 1210 1252 >( 1211 - "SELECT id, user_id, filename, status FROM transcriptions WHERE id = ?", 1253 + "SELECT id, user_id, class_id, filename, status FROM transcriptions WHERE id = ?", 1212 1254 ) 1213 1255 .get(transcriptionId); 1214 1256 ··· 1219 1261 ); 1220 1262 } 1221 1263 1222 - // Allow access if user owns it or is admin 1223 - if (transcription.user_id !== user.id && user.role !== "admin") { 1264 + // Check access permissions 1265 + const isOwner = transcription.user_id === user.id; 1266 + const isAdmin = user.role === "admin"; 1267 + let isClassMember = false; 1268 + 1269 + // If transcription belongs to a class, check enrollment 1270 + if (transcription.class_id) { 1271 + isClassMember = isUserEnrolledInClass(user.id, transcription.class_id); 1272 + } 1273 + 1274 + // Allow access if: owner, admin, or enrolled in the class 1275 + if (!isOwner && !isAdmin && !isClassMember) { 1224 1276 return Response.json( 1225 1277 { error: "Transcription not found" }, 1226 1278 { status: 404 }, 1227 1279 ); 1280 + } 1281 + 1282 + // Require subscription only if accessing own transcription (not class) 1283 + if (isOwner && !transcription.class_id && !isAdmin && !hasActiveSubscription(user.id)) { 1284 + throw AuthErrors.subscriptionRequired(); 1228 1285 } 1229 1286 1230 1287 // For pending recordings, audio file exists even though transcription isn't complete