@playcademy/sandbox 0.9.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +62 -145
- package/dist/server.js +62 -145
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1128,7 +1128,7 @@ var package_default;
|
|
|
1128
1128
|
var init_package = __esm(() => {
|
|
1129
1129
|
package_default = {
|
|
1130
1130
|
name: "@playcademy/sandbox",
|
|
1131
|
-
version: "0.
|
|
1131
|
+
version: "0.10.0",
|
|
1132
1132
|
description: "Local development server for Playcademy game development",
|
|
1133
1133
|
type: "module",
|
|
1134
1134
|
exports: {
|
|
@@ -96840,10 +96840,12 @@ function compileResponseProcessing(item, itemIdentifier, maxScore, declarations)
|
|
|
96840
96840
|
if (!onlyAttributes(processing, []) || !onlyWhitespaceText(processing.children) || processingRules.length === 0 || processingRules.some((rule) => rule.localName !== "qti-response-condition")) {
|
|
96841
96841
|
return fallback(itemIdentifier, maxScore, "unsupported-response-processing");
|
|
96842
96842
|
}
|
|
96843
|
-
if (processingRules.length === 1 && declarations.size === 1 &&
|
|
96843
|
+
if (processingRules.length === 1 && declarations.size === 1 && feedbackOutcomeIsDeclared(item)) {
|
|
96844
96844
|
const declaration = [...declarations.values()][0];
|
|
96845
96845
|
const rule = binaryBranchRule(processingRules[0], declaration);
|
|
96846
|
-
|
|
96846
|
+
const scoreIsDeclared = childElements(item, "qti-outcome-declaration").some((outcome) => outcome.attributes.identifier?.trim().toUpperCase() === "SCORE");
|
|
96847
|
+
const completeIdentifierMatch = rule?.comparison.kind === "match" && rule.baseType === "identifier" && (rule.cardinality === "single" || rule.cardinality === "multiple");
|
|
96848
|
+
if (rule && (scoreDefaultIsZero(item) || !scoreIsDeclared && completeIdentifierMatch)) {
|
|
96847
96849
|
return platformResult(itemIdentifier, maxScore, [rule]);
|
|
96848
96850
|
}
|
|
96849
96851
|
}
|
|
@@ -99791,7 +99793,7 @@ function scoringSchema(identity) {
|
|
|
99791
99793
|
if (identity.kind === "assessment") {
|
|
99792
99794
|
return exports_external.object({
|
|
99793
99795
|
items: assessmentScoringArtifactItemsSchema2,
|
|
99794
|
-
reviewBank: exports_external.undefined(),
|
|
99796
|
+
reviewBank: exports_external.undefined().optional(),
|
|
99795
99797
|
diagnostic: diagnosticContextSchema.optional()
|
|
99796
99798
|
}).strict();
|
|
99797
99799
|
}
|
|
@@ -99805,7 +99807,7 @@ function scoringSchema(identity) {
|
|
|
99805
99807
|
standards: exports_external.array(exports_external.unknown())
|
|
99806
99808
|
}).strict())
|
|
99807
99809
|
}).strict(),
|
|
99808
|
-
diagnostic: exports_external.undefined()
|
|
99810
|
+
diagnostic: exports_external.undefined().optional()
|
|
99809
99811
|
}).strict().refine((payload) => payload.reviewBank.items.length === payload.items.length && payload.reviewBank.items.every((item, index2) => item.itemIdentifier === payload.items[index2]?.itemIdentifier));
|
|
99810
99812
|
}
|
|
99811
99813
|
function assertPlayableAssessmentArtifact(payload, identity) {
|
|
@@ -100011,7 +100013,7 @@ var init_assessment_artifact_runtime_util = __esm(() => {
|
|
|
100011
100013
|
}).strict();
|
|
100012
100014
|
diagnosticContextSchema = exports_external.object({
|
|
100013
100015
|
routingRevision: trimmedNonEmptyString3,
|
|
100014
|
-
routingManifest: exports_external.record(exports_external.unknown())
|
|
100016
|
+
routingManifest: exports_external.record(exports_external.string(), exports_external.unknown())
|
|
100015
100017
|
}).strict();
|
|
100016
100018
|
});
|
|
100017
100019
|
|
|
@@ -110749,10 +110751,10 @@ function createSandboxAuthProvider() {
|
|
|
110749
110751
|
},
|
|
110750
110752
|
async mintGameToken(gameId, userId) {
|
|
110751
110753
|
const header = btoa(JSON.stringify({ alg: "none", typ: "sandbox" }));
|
|
110752
|
-
const exp = Date.now() + 15 * 60
|
|
110754
|
+
const exp = Math.floor(Date.now() / 1000) + 15 * 60;
|
|
110753
110755
|
const payload = btoa(JSON.stringify({ sub: gameId, uid: userId, exp }));
|
|
110754
110756
|
const token = `${header}.${payload}.sandbox`;
|
|
110755
|
-
return { token, exp };
|
|
110757
|
+
return { token, exp: exp * 1000 };
|
|
110756
110758
|
},
|
|
110757
110759
|
async validateGameToken(token) {
|
|
110758
110760
|
try {
|
|
@@ -110760,18 +110762,9 @@ function createSandboxAuthProvider() {
|
|
|
110760
110762
|
if (parts2.length !== 3) {
|
|
110761
110763
|
return null;
|
|
110762
110764
|
}
|
|
110763
|
-
if (parts2[2] === "sandbox") {
|
|
110764
|
-
const payload2 = JSON.parse(atob(parts2[1]));
|
|
110765
|
-
if (payload2.sub && payload2.uid) {
|
|
110766
|
-
if (payload2.exp && payload2.exp < Date.now()) {
|
|
110767
|
-
log.debug("[SandboxAuthProvider] Token expired");
|
|
110768
|
-
return null;
|
|
110769
|
-
}
|
|
110770
|
-
return payload2;
|
|
110771
|
-
}
|
|
110772
|
-
}
|
|
110773
110765
|
const payload = JSON.parse(atob(parts2[1]));
|
|
110774
|
-
|
|
110766
|
+
const nowSeconds = Date.now() / 1000;
|
|
110767
|
+
if (typeof payload?.sub === "string" && payload.sub.length > 0 && typeof payload.uid === "string" && payload.uid.length > 0 && typeof payload.exp === "number" && Number.isFinite(payload.exp) && payload.exp > nowSeconds && payload.exp < nowSeconds + MAX_TOKEN_LIFETIME_SECONDS) {
|
|
110775
110768
|
return payload;
|
|
110776
110769
|
}
|
|
110777
110770
|
return null;
|
|
@@ -110869,7 +110862,7 @@ function createSandboxAuthProvider() {
|
|
|
110869
110862
|
}
|
|
110870
110863
|
};
|
|
110871
110864
|
}
|
|
110872
|
-
var sandboxApiKeys, sandboxAssessmentPreparationReceipts, ASSESSMENT_PREPARATION_RECEIPT_LIFETIME_MS, sandboxAssessmentTokens;
|
|
110865
|
+
var sandboxApiKeys, sandboxAssessmentPreparationReceipts, ASSESSMENT_PREPARATION_RECEIPT_LIFETIME_MS, MAX_TOKEN_LIFETIME_SECONDS, sandboxAssessmentTokens;
|
|
110873
110866
|
var init_auth_provider = __esm(async () => {
|
|
110874
110867
|
init_src6();
|
|
110875
110868
|
init_constants();
|
|
@@ -110877,6 +110870,7 @@ var init_auth_provider = __esm(async () => {
|
|
|
110877
110870
|
sandboxApiKeys = new Map;
|
|
110878
110871
|
sandboxAssessmentPreparationReceipts = new Map;
|
|
110879
110872
|
ASSESSMENT_PREPARATION_RECEIPT_LIFETIME_MS = 5 * 60 * 1000;
|
|
110873
|
+
MAX_TOKEN_LIFETIME_SECONDS = 30 * 24 * 60 * 60;
|
|
110880
110874
|
sandboxAssessmentTokens = new TimebackCache({
|
|
110881
110875
|
defaultTTL: 2 * 60 * 60 * 1000,
|
|
110882
110876
|
maxSize: 1000
|
|
@@ -112904,56 +112898,13 @@ function extractBearerToken(authHeader) {
|
|
|
112904
112898
|
}
|
|
112905
112899
|
return authHeader.substring(7);
|
|
112906
112900
|
}
|
|
112907
|
-
function
|
|
112908
|
-
try {
|
|
112909
|
-
const parts2 = token.split(".");
|
|
112910
|
-
if (parts2.length !== 3 || parts2[2] !== "sandbox") {
|
|
112911
|
-
return null;
|
|
112912
|
-
}
|
|
112913
|
-
const header = JSON.parse(atob(parts2[0]));
|
|
112914
|
-
if (header.typ !== "sandbox") {
|
|
112915
|
-
return null;
|
|
112916
|
-
}
|
|
112917
|
-
const payload = JSON.parse(atob(parts2[1]));
|
|
112918
|
-
if (!payload.uid || !payload.sub) {
|
|
112919
|
-
return null;
|
|
112920
|
-
}
|
|
112921
|
-
return {
|
|
112922
|
-
userId: payload.uid,
|
|
112923
|
-
gameSlug: payload.sub
|
|
112924
|
-
};
|
|
112925
|
-
} catch {
|
|
112926
|
-
return null;
|
|
112927
|
-
}
|
|
112928
|
-
}
|
|
112929
|
-
function parseJwtClaims(token) {
|
|
112930
|
-
try {
|
|
112931
|
-
const parts2 = token.split(".");
|
|
112932
|
-
if (parts2.length === 3 && parts2[1]) {
|
|
112933
|
-
const payload = JSON.parse(atob(parts2[1]));
|
|
112934
|
-
if (payload.uid) {
|
|
112935
|
-
return {
|
|
112936
|
-
userId: payload.uid,
|
|
112937
|
-
gameId: payload.sub
|
|
112938
|
-
};
|
|
112939
|
-
}
|
|
112940
|
-
}
|
|
112941
|
-
} catch {}
|
|
112942
|
-
return null;
|
|
112943
|
-
}
|
|
112944
|
-
function resolveAuth(token) {
|
|
112901
|
+
async function resolveAuth(token) {
|
|
112945
112902
|
const demoUser = DEMO_TOKENS[token];
|
|
112946
112903
|
if (demoUser) {
|
|
112947
112904
|
return { userId: demoUser.id };
|
|
112948
112905
|
}
|
|
112949
|
-
|
|
112950
|
-
|
|
112951
|
-
if (sandboxClaims) {
|
|
112952
|
-
return sandboxClaims;
|
|
112953
|
-
}
|
|
112954
|
-
return parseJwtClaims(token);
|
|
112955
|
-
}
|
|
112956
|
-
return null;
|
|
112906
|
+
const payload = await sandboxAuth.validateGameToken(token);
|
|
112907
|
+
return payload ? { userId: payload.uid, gameId: payload.sub } : null;
|
|
112957
112908
|
}
|
|
112958
112909
|
async function fetchUserFromDatabase(db2, userId) {
|
|
112959
112910
|
try {
|
|
@@ -112966,18 +112917,6 @@ async function fetchUserFromDatabase(db2, userId) {
|
|
|
112966
112917
|
throw error88;
|
|
112967
112918
|
}
|
|
112968
112919
|
}
|
|
112969
|
-
async function resolveGameIdFromSlug(db2, slug2) {
|
|
112970
|
-
try {
|
|
112971
|
-
const game2 = await db2.query.games.findFirst({
|
|
112972
|
-
where: eq(games.slug, slug2),
|
|
112973
|
-
columns: { id: true }
|
|
112974
|
-
});
|
|
112975
|
-
return game2?.id || null;
|
|
112976
|
-
} catch (error88) {
|
|
112977
|
-
console.error("[Auth] Error looking up game by slug:", error88);
|
|
112978
|
-
return null;
|
|
112979
|
-
}
|
|
112980
|
-
}
|
|
112981
112920
|
function isPublicRoute(path, exceptions) {
|
|
112982
112921
|
return exceptions.some((exception) => {
|
|
112983
112922
|
if (path === exception) {
|
|
@@ -112995,35 +112934,23 @@ async function authenticateRequest(c) {
|
|
|
112995
112934
|
const apiKey = c.req.header("x-api-key");
|
|
112996
112935
|
const token = bearerToken || apiKey;
|
|
112997
112936
|
if (!token) {
|
|
112998
|
-
return {
|
|
112999
|
-
success: false,
|
|
113000
|
-
error: "No authorization token provided",
|
|
113001
|
-
shouldReturn404: true
|
|
113002
|
-
};
|
|
112937
|
+
return { success: false, error: "No authorization token provided" };
|
|
113003
112938
|
}
|
|
113004
|
-
const resolved = resolveAuth(token);
|
|
112939
|
+
const resolved = await resolveAuth(token);
|
|
113005
112940
|
if (!resolved) {
|
|
113006
|
-
return {
|
|
113007
|
-
success: false,
|
|
113008
|
-
error: "Invalid or expired token",
|
|
113009
|
-
shouldReturn404: true
|
|
113010
|
-
};
|
|
112941
|
+
return { success: false, error: "Invalid or expired token" };
|
|
113011
112942
|
}
|
|
113012
112943
|
const claims = resolved;
|
|
113013
112944
|
const db2 = c.get("db");
|
|
113014
112945
|
if (!db2) {
|
|
113015
112946
|
console.error("[Auth] Database not available in context");
|
|
113016
|
-
return { success: false, error: "Internal server error"
|
|
112947
|
+
return { success: false, error: "Internal server error" };
|
|
113017
112948
|
}
|
|
113018
112949
|
const user = await fetchUserFromDatabase(db2, claims.userId);
|
|
113019
112950
|
if (!user) {
|
|
113020
|
-
return { success: false, error: "User not found or token invalid"
|
|
112951
|
+
return { success: false, error: "User not found or token invalid" };
|
|
113021
112952
|
}
|
|
113022
|
-
|
|
113023
|
-
if (!gameId && claims.gameSlug) {
|
|
113024
|
-
gameId = await resolveGameIdFromSlug(db2, claims.gameSlug) ?? undefined;
|
|
113025
|
-
}
|
|
113026
|
-
return { success: true, user, gameId };
|
|
112953
|
+
return { success: true, user, gameId: claims.gameId };
|
|
113027
112954
|
}
|
|
113028
112955
|
function setupAuth(options = {}) {
|
|
113029
112956
|
const { exceptions = [] } = options;
|
|
@@ -113043,20 +112970,19 @@ function setupAuth(options = {}) {
|
|
|
113043
112970
|
}
|
|
113044
112971
|
c.set("authError", result.error);
|
|
113045
112972
|
await next();
|
|
113046
|
-
if (result.shouldReturn404 && c.res.status === 404) {
|
|
113047
|
-
return;
|
|
113048
|
-
}
|
|
113049
|
-
return c.json({ error: "Unauthorized", message: result.error }, 401);
|
|
113050
112973
|
} catch (error88) {
|
|
113051
112974
|
console.error("[Auth] Unexpected error in authentication middleware:", error88);
|
|
113052
112975
|
return c.json({ error: "Internal Server Error", message: "Authentication failed" }, 500);
|
|
113053
112976
|
}
|
|
113054
112977
|
};
|
|
113055
112978
|
}
|
|
113056
|
-
var
|
|
112979
|
+
var sandboxAuth;
|
|
112980
|
+
var init_auth2 = __esm(async () => {
|
|
113057
112981
|
init_drizzle_orm();
|
|
113058
112982
|
init_tables_index();
|
|
113059
112983
|
init_constants();
|
|
112984
|
+
await init_providers();
|
|
112985
|
+
sandboxAuth = createSandboxAuthProvider();
|
|
113060
112986
|
});
|
|
113061
112987
|
|
|
113062
112988
|
// src/server/app.ts
|
|
@@ -113075,11 +113001,11 @@ function createApp(db2, options) {
|
|
|
113075
113001
|
}));
|
|
113076
113002
|
return app;
|
|
113077
113003
|
}
|
|
113078
|
-
var init_app = __esm(() => {
|
|
113004
|
+
var init_app = __esm(async () => {
|
|
113079
113005
|
init_dist7();
|
|
113080
113006
|
init_cors();
|
|
113081
113007
|
init_logger2();
|
|
113082
|
-
init_auth2();
|
|
113008
|
+
await init_auth2();
|
|
113083
113009
|
});
|
|
113084
113010
|
|
|
113085
113011
|
// ../../node_modules/.bun/@electric-sql+pglite@0.3.16/node_modules/@electric-sql/pglite/dist/chunk-QY3QWFKW.js
|
|
@@ -169208,18 +169134,33 @@ var init_sessions = __esm(async () => {
|
|
|
169208
169134
|
});
|
|
169209
169135
|
|
|
169210
169136
|
// src/routes/platform/games/verify.ts
|
|
169211
|
-
|
|
169137
|
+
function toUserInfo(userData) {
|
|
169138
|
+
return {
|
|
169139
|
+
sub: userData.id,
|
|
169140
|
+
email: userData.email || "",
|
|
169141
|
+
name: userData.name || userData.username || "",
|
|
169142
|
+
email_verified: true,
|
|
169143
|
+
given_name: undefined,
|
|
169144
|
+
family_name: undefined,
|
|
169145
|
+
timeback_id: userData.timebackId || undefined
|
|
169146
|
+
};
|
|
169147
|
+
}
|
|
169148
|
+
var gameVerifyRouter, sandboxAuth2;
|
|
169212
169149
|
var init_verify = __esm(async () => {
|
|
169213
169150
|
init_dist7();
|
|
169214
169151
|
init_errors2();
|
|
169215
169152
|
init_constants();
|
|
169216
169153
|
init_error_handler();
|
|
169217
|
-
await
|
|
169154
|
+
await __promiseAll([
|
|
169155
|
+
init_api3(),
|
|
169156
|
+
init_providers()
|
|
169157
|
+
]);
|
|
169218
169158
|
gameVerifyRouter = new Hono2;
|
|
169159
|
+
sandboxAuth2 = createSandboxAuthProvider();
|
|
169219
169160
|
gameVerifyRouter.post("/verify", async (c2) => {
|
|
169220
169161
|
const body2 = await c2.req.json().catch(() => ({}));
|
|
169221
169162
|
const token = body2?.token;
|
|
169222
|
-
if (!token) {
|
|
169163
|
+
if (typeof token !== "string" || !token) {
|
|
169223
169164
|
return c2.json({ error: "Missing token" }, 400);
|
|
169224
169165
|
}
|
|
169225
169166
|
const demoUser = DEMO_TOKENS[token];
|
|
@@ -169232,49 +169173,25 @@ var init_verify = __esm(async () => {
|
|
|
169232
169173
|
return c2.json({ error: "User not found in sandbox" }, 500);
|
|
169233
169174
|
}
|
|
169234
169175
|
return c2.json({
|
|
169235
|
-
claims: { sandbox: true },
|
|
169176
|
+
claims: { sandbox: true, exp: Math.floor(Date.now() / 1000) + 15 * 60 },
|
|
169236
169177
|
gameId: MOCK_GAME_ID,
|
|
169237
|
-
user:
|
|
169238
|
-
sub: userData.id,
|
|
169239
|
-
email: userData.email || "",
|
|
169240
|
-
name: userData.name || userData.username || "",
|
|
169241
|
-
email_verified: true,
|
|
169242
|
-
given_name: undefined,
|
|
169243
|
-
family_name: undefined,
|
|
169244
|
-
timeback_id: userData.timebackId || undefined
|
|
169245
|
-
}
|
|
169178
|
+
user: toUserInfo(userData)
|
|
169246
169179
|
});
|
|
169247
169180
|
}
|
|
169248
169181
|
if (token.endsWith(".sandbox")) {
|
|
169182
|
+
const payload = await sandboxAuth2.validateGameToken(token);
|
|
169183
|
+
if (!payload) {
|
|
169184
|
+
return c2.json({ error: "Invalid or expired sandbox token" }, 400);
|
|
169185
|
+
}
|
|
169249
169186
|
try {
|
|
169250
|
-
const
|
|
169251
|
-
|
|
169252
|
-
|
|
169253
|
-
|
|
169254
|
-
|
|
169255
|
-
|
|
169256
|
-
const userData = await db2.query.users.findFirst({
|
|
169257
|
-
where: (users3, { eq: eq3 }) => eq3(users3.id, userId)
|
|
169258
|
-
});
|
|
169259
|
-
if (!userData) {
|
|
169260
|
-
return c2.json({ error: "User not found in sandbox" }, 500);
|
|
169261
|
-
}
|
|
169262
|
-
return c2.json({
|
|
169263
|
-
claims: payload,
|
|
169264
|
-
gameId: gameIdOrSlug,
|
|
169265
|
-
user: {
|
|
169266
|
-
sub: userData.id,
|
|
169267
|
-
email: userData.email || "",
|
|
169268
|
-
name: userData.name || userData.username || "",
|
|
169269
|
-
email_verified: true,
|
|
169270
|
-
given_name: undefined,
|
|
169271
|
-
family_name: undefined,
|
|
169272
|
-
timeback_id: userData.timebackId || undefined
|
|
169273
|
-
}
|
|
169274
|
-
});
|
|
169275
|
-
} else {
|
|
169276
|
-
throw new Error("Malformed sandbox token: expected 3 parts");
|
|
169187
|
+
const db2 = c2.get("db");
|
|
169188
|
+
const userData = await db2.query.users.findFirst({
|
|
169189
|
+
where: (users3, { eq: eq3 }) => eq3(users3.id, payload.uid)
|
|
169190
|
+
});
|
|
169191
|
+
if (!userData) {
|
|
169192
|
+
return c2.json({ error: "User not found in sandbox" }, 500);
|
|
169277
169193
|
}
|
|
169194
|
+
return c2.json({ claims: payload, gameId: payload.sub, user: toUserInfo(userData) });
|
|
169278
169195
|
} catch {
|
|
169279
169196
|
return c2.json({ error: "Invalid sandbox token" }, 400);
|
|
169280
169197
|
}
|
|
@@ -169782,13 +169699,13 @@ var init_server = __esm(async () => {
|
|
|
169782
169699
|
init_port();
|
|
169783
169700
|
init_package();
|
|
169784
169701
|
init_config();
|
|
169785
|
-
init_app();
|
|
169786
169702
|
init_database2();
|
|
169787
169703
|
init_options();
|
|
169788
169704
|
await __promiseAll([
|
|
169789
169705
|
init_api3(),
|
|
169790
169706
|
init_context(),
|
|
169791
|
-
init_providers()
|
|
169707
|
+
init_providers(),
|
|
169708
|
+
init_app()
|
|
169792
169709
|
]);
|
|
169793
169710
|
version5 = package_default.version;
|
|
169794
169711
|
});
|
package/dist/server.js
CHANGED
|
@@ -1127,7 +1127,7 @@ var package_default;
|
|
|
1127
1127
|
var init_package = __esm(() => {
|
|
1128
1128
|
package_default = {
|
|
1129
1129
|
name: "@playcademy/sandbox",
|
|
1130
|
-
version: "0.
|
|
1130
|
+
version: "0.10.0",
|
|
1131
1131
|
description: "Local development server for Playcademy game development",
|
|
1132
1132
|
type: "module",
|
|
1133
1133
|
exports: {
|
|
@@ -96839,10 +96839,12 @@ function compileResponseProcessing(item, itemIdentifier, maxScore, declarations)
|
|
|
96839
96839
|
if (!onlyAttributes(processing, []) || !onlyWhitespaceText(processing.children) || processingRules.length === 0 || processingRules.some((rule) => rule.localName !== "qti-response-condition")) {
|
|
96840
96840
|
return fallback(itemIdentifier, maxScore, "unsupported-response-processing");
|
|
96841
96841
|
}
|
|
96842
|
-
if (processingRules.length === 1 && declarations.size === 1 &&
|
|
96842
|
+
if (processingRules.length === 1 && declarations.size === 1 && feedbackOutcomeIsDeclared(item)) {
|
|
96843
96843
|
const declaration = [...declarations.values()][0];
|
|
96844
96844
|
const rule = binaryBranchRule(processingRules[0], declaration);
|
|
96845
|
-
|
|
96845
|
+
const scoreIsDeclared = childElements(item, "qti-outcome-declaration").some((outcome) => outcome.attributes.identifier?.trim().toUpperCase() === "SCORE");
|
|
96846
|
+
const completeIdentifierMatch = rule?.comparison.kind === "match" && rule.baseType === "identifier" && (rule.cardinality === "single" || rule.cardinality === "multiple");
|
|
96847
|
+
if (rule && (scoreDefaultIsZero(item) || !scoreIsDeclared && completeIdentifierMatch)) {
|
|
96846
96848
|
return platformResult(itemIdentifier, maxScore, [rule]);
|
|
96847
96849
|
}
|
|
96848
96850
|
}
|
|
@@ -99790,7 +99792,7 @@ function scoringSchema(identity) {
|
|
|
99790
99792
|
if (identity.kind === "assessment") {
|
|
99791
99793
|
return exports_external.object({
|
|
99792
99794
|
items: assessmentScoringArtifactItemsSchema2,
|
|
99793
|
-
reviewBank: exports_external.undefined(),
|
|
99795
|
+
reviewBank: exports_external.undefined().optional(),
|
|
99794
99796
|
diagnostic: diagnosticContextSchema.optional()
|
|
99795
99797
|
}).strict();
|
|
99796
99798
|
}
|
|
@@ -99804,7 +99806,7 @@ function scoringSchema(identity) {
|
|
|
99804
99806
|
standards: exports_external.array(exports_external.unknown())
|
|
99805
99807
|
}).strict())
|
|
99806
99808
|
}).strict(),
|
|
99807
|
-
diagnostic: exports_external.undefined()
|
|
99809
|
+
diagnostic: exports_external.undefined().optional()
|
|
99808
99810
|
}).strict().refine((payload) => payload.reviewBank.items.length === payload.items.length && payload.reviewBank.items.every((item, index2) => item.itemIdentifier === payload.items[index2]?.itemIdentifier));
|
|
99809
99811
|
}
|
|
99810
99812
|
function assertPlayableAssessmentArtifact(payload, identity) {
|
|
@@ -100010,7 +100012,7 @@ var init_assessment_artifact_runtime_util = __esm(() => {
|
|
|
100010
100012
|
}).strict();
|
|
100011
100013
|
diagnosticContextSchema = exports_external.object({
|
|
100012
100014
|
routingRevision: trimmedNonEmptyString3,
|
|
100013
|
-
routingManifest: exports_external.record(exports_external.unknown())
|
|
100015
|
+
routingManifest: exports_external.record(exports_external.string(), exports_external.unknown())
|
|
100014
100016
|
}).strict();
|
|
100015
100017
|
});
|
|
100016
100018
|
|
|
@@ -110748,10 +110750,10 @@ function createSandboxAuthProvider() {
|
|
|
110748
110750
|
},
|
|
110749
110751
|
async mintGameToken(gameId, userId) {
|
|
110750
110752
|
const header = btoa(JSON.stringify({ alg: "none", typ: "sandbox" }));
|
|
110751
|
-
const exp = Date.now() + 15 * 60
|
|
110753
|
+
const exp = Math.floor(Date.now() / 1000) + 15 * 60;
|
|
110752
110754
|
const payload = btoa(JSON.stringify({ sub: gameId, uid: userId, exp }));
|
|
110753
110755
|
const token = `${header}.${payload}.sandbox`;
|
|
110754
|
-
return { token, exp };
|
|
110756
|
+
return { token, exp: exp * 1000 };
|
|
110755
110757
|
},
|
|
110756
110758
|
async validateGameToken(token) {
|
|
110757
110759
|
try {
|
|
@@ -110759,18 +110761,9 @@ function createSandboxAuthProvider() {
|
|
|
110759
110761
|
if (parts2.length !== 3) {
|
|
110760
110762
|
return null;
|
|
110761
110763
|
}
|
|
110762
|
-
if (parts2[2] === "sandbox") {
|
|
110763
|
-
const payload2 = JSON.parse(atob(parts2[1]));
|
|
110764
|
-
if (payload2.sub && payload2.uid) {
|
|
110765
|
-
if (payload2.exp && payload2.exp < Date.now()) {
|
|
110766
|
-
log.debug("[SandboxAuthProvider] Token expired");
|
|
110767
|
-
return null;
|
|
110768
|
-
}
|
|
110769
|
-
return payload2;
|
|
110770
|
-
}
|
|
110771
|
-
}
|
|
110772
110764
|
const payload = JSON.parse(atob(parts2[1]));
|
|
110773
|
-
|
|
110765
|
+
const nowSeconds = Date.now() / 1000;
|
|
110766
|
+
if (typeof payload?.sub === "string" && payload.sub.length > 0 && typeof payload.uid === "string" && payload.uid.length > 0 && typeof payload.exp === "number" && Number.isFinite(payload.exp) && payload.exp > nowSeconds && payload.exp < nowSeconds + MAX_TOKEN_LIFETIME_SECONDS) {
|
|
110774
110767
|
return payload;
|
|
110775
110768
|
}
|
|
110776
110769
|
return null;
|
|
@@ -110868,7 +110861,7 @@ function createSandboxAuthProvider() {
|
|
|
110868
110861
|
}
|
|
110869
110862
|
};
|
|
110870
110863
|
}
|
|
110871
|
-
var sandboxApiKeys, sandboxAssessmentPreparationReceipts, ASSESSMENT_PREPARATION_RECEIPT_LIFETIME_MS, sandboxAssessmentTokens;
|
|
110864
|
+
var sandboxApiKeys, sandboxAssessmentPreparationReceipts, ASSESSMENT_PREPARATION_RECEIPT_LIFETIME_MS, MAX_TOKEN_LIFETIME_SECONDS, sandboxAssessmentTokens;
|
|
110872
110865
|
var init_auth_provider = __esm(async () => {
|
|
110873
110866
|
init_src6();
|
|
110874
110867
|
init_constants();
|
|
@@ -110876,6 +110869,7 @@ var init_auth_provider = __esm(async () => {
|
|
|
110876
110869
|
sandboxApiKeys = new Map;
|
|
110877
110870
|
sandboxAssessmentPreparationReceipts = new Map;
|
|
110878
110871
|
ASSESSMENT_PREPARATION_RECEIPT_LIFETIME_MS = 5 * 60 * 1000;
|
|
110872
|
+
MAX_TOKEN_LIFETIME_SECONDS = 30 * 24 * 60 * 60;
|
|
110879
110873
|
sandboxAssessmentTokens = new TimebackCache({
|
|
110880
110874
|
defaultTTL: 2 * 60 * 60 * 1000,
|
|
110881
110875
|
maxSize: 1000
|
|
@@ -112903,56 +112897,13 @@ function extractBearerToken(authHeader) {
|
|
|
112903
112897
|
}
|
|
112904
112898
|
return authHeader.substring(7);
|
|
112905
112899
|
}
|
|
112906
|
-
function
|
|
112907
|
-
try {
|
|
112908
|
-
const parts2 = token.split(".");
|
|
112909
|
-
if (parts2.length !== 3 || parts2[2] !== "sandbox") {
|
|
112910
|
-
return null;
|
|
112911
|
-
}
|
|
112912
|
-
const header = JSON.parse(atob(parts2[0]));
|
|
112913
|
-
if (header.typ !== "sandbox") {
|
|
112914
|
-
return null;
|
|
112915
|
-
}
|
|
112916
|
-
const payload = JSON.parse(atob(parts2[1]));
|
|
112917
|
-
if (!payload.uid || !payload.sub) {
|
|
112918
|
-
return null;
|
|
112919
|
-
}
|
|
112920
|
-
return {
|
|
112921
|
-
userId: payload.uid,
|
|
112922
|
-
gameSlug: payload.sub
|
|
112923
|
-
};
|
|
112924
|
-
} catch {
|
|
112925
|
-
return null;
|
|
112926
|
-
}
|
|
112927
|
-
}
|
|
112928
|
-
function parseJwtClaims(token) {
|
|
112929
|
-
try {
|
|
112930
|
-
const parts2 = token.split(".");
|
|
112931
|
-
if (parts2.length === 3 && parts2[1]) {
|
|
112932
|
-
const payload = JSON.parse(atob(parts2[1]));
|
|
112933
|
-
if (payload.uid) {
|
|
112934
|
-
return {
|
|
112935
|
-
userId: payload.uid,
|
|
112936
|
-
gameId: payload.sub
|
|
112937
|
-
};
|
|
112938
|
-
}
|
|
112939
|
-
}
|
|
112940
|
-
} catch {}
|
|
112941
|
-
return null;
|
|
112942
|
-
}
|
|
112943
|
-
function resolveAuth(token) {
|
|
112900
|
+
async function resolveAuth(token) {
|
|
112944
112901
|
const demoUser = DEMO_TOKENS[token];
|
|
112945
112902
|
if (demoUser) {
|
|
112946
112903
|
return { userId: demoUser.id };
|
|
112947
112904
|
}
|
|
112948
|
-
|
|
112949
|
-
|
|
112950
|
-
if (sandboxClaims) {
|
|
112951
|
-
return sandboxClaims;
|
|
112952
|
-
}
|
|
112953
|
-
return parseJwtClaims(token);
|
|
112954
|
-
}
|
|
112955
|
-
return null;
|
|
112905
|
+
const payload = await sandboxAuth.validateGameToken(token);
|
|
112906
|
+
return payload ? { userId: payload.uid, gameId: payload.sub } : null;
|
|
112956
112907
|
}
|
|
112957
112908
|
async function fetchUserFromDatabase(db2, userId) {
|
|
112958
112909
|
try {
|
|
@@ -112965,18 +112916,6 @@ async function fetchUserFromDatabase(db2, userId) {
|
|
|
112965
112916
|
throw error88;
|
|
112966
112917
|
}
|
|
112967
112918
|
}
|
|
112968
|
-
async function resolveGameIdFromSlug(db2, slug2) {
|
|
112969
|
-
try {
|
|
112970
|
-
const game2 = await db2.query.games.findFirst({
|
|
112971
|
-
where: eq(games.slug, slug2),
|
|
112972
|
-
columns: { id: true }
|
|
112973
|
-
});
|
|
112974
|
-
return game2?.id || null;
|
|
112975
|
-
} catch (error88) {
|
|
112976
|
-
console.error("[Auth] Error looking up game by slug:", error88);
|
|
112977
|
-
return null;
|
|
112978
|
-
}
|
|
112979
|
-
}
|
|
112980
112919
|
function isPublicRoute(path, exceptions) {
|
|
112981
112920
|
return exceptions.some((exception) => {
|
|
112982
112921
|
if (path === exception) {
|
|
@@ -112994,35 +112933,23 @@ async function authenticateRequest(c) {
|
|
|
112994
112933
|
const apiKey = c.req.header("x-api-key");
|
|
112995
112934
|
const token = bearerToken || apiKey;
|
|
112996
112935
|
if (!token) {
|
|
112997
|
-
return {
|
|
112998
|
-
success: false,
|
|
112999
|
-
error: "No authorization token provided",
|
|
113000
|
-
shouldReturn404: true
|
|
113001
|
-
};
|
|
112936
|
+
return { success: false, error: "No authorization token provided" };
|
|
113002
112937
|
}
|
|
113003
|
-
const resolved = resolveAuth(token);
|
|
112938
|
+
const resolved = await resolveAuth(token);
|
|
113004
112939
|
if (!resolved) {
|
|
113005
|
-
return {
|
|
113006
|
-
success: false,
|
|
113007
|
-
error: "Invalid or expired token",
|
|
113008
|
-
shouldReturn404: true
|
|
113009
|
-
};
|
|
112940
|
+
return { success: false, error: "Invalid or expired token" };
|
|
113010
112941
|
}
|
|
113011
112942
|
const claims = resolved;
|
|
113012
112943
|
const db2 = c.get("db");
|
|
113013
112944
|
if (!db2) {
|
|
113014
112945
|
console.error("[Auth] Database not available in context");
|
|
113015
|
-
return { success: false, error: "Internal server error"
|
|
112946
|
+
return { success: false, error: "Internal server error" };
|
|
113016
112947
|
}
|
|
113017
112948
|
const user = await fetchUserFromDatabase(db2, claims.userId);
|
|
113018
112949
|
if (!user) {
|
|
113019
|
-
return { success: false, error: "User not found or token invalid"
|
|
112950
|
+
return { success: false, error: "User not found or token invalid" };
|
|
113020
112951
|
}
|
|
113021
|
-
|
|
113022
|
-
if (!gameId && claims.gameSlug) {
|
|
113023
|
-
gameId = await resolveGameIdFromSlug(db2, claims.gameSlug) ?? undefined;
|
|
113024
|
-
}
|
|
113025
|
-
return { success: true, user, gameId };
|
|
112952
|
+
return { success: true, user, gameId: claims.gameId };
|
|
113026
112953
|
}
|
|
113027
112954
|
function setupAuth(options = {}) {
|
|
113028
112955
|
const { exceptions = [] } = options;
|
|
@@ -113042,20 +112969,19 @@ function setupAuth(options = {}) {
|
|
|
113042
112969
|
}
|
|
113043
112970
|
c.set("authError", result.error);
|
|
113044
112971
|
await next();
|
|
113045
|
-
if (result.shouldReturn404 && c.res.status === 404) {
|
|
113046
|
-
return;
|
|
113047
|
-
}
|
|
113048
|
-
return c.json({ error: "Unauthorized", message: result.error }, 401);
|
|
113049
112972
|
} catch (error88) {
|
|
113050
112973
|
console.error("[Auth] Unexpected error in authentication middleware:", error88);
|
|
113051
112974
|
return c.json({ error: "Internal Server Error", message: "Authentication failed" }, 500);
|
|
113052
112975
|
}
|
|
113053
112976
|
};
|
|
113054
112977
|
}
|
|
113055
|
-
var
|
|
112978
|
+
var sandboxAuth;
|
|
112979
|
+
var init_auth2 = __esm(async () => {
|
|
113056
112980
|
init_drizzle_orm();
|
|
113057
112981
|
init_tables_index();
|
|
113058
112982
|
init_constants();
|
|
112983
|
+
await init_providers();
|
|
112984
|
+
sandboxAuth = createSandboxAuthProvider();
|
|
113059
112985
|
});
|
|
113060
112986
|
|
|
113061
112987
|
// src/server/app.ts
|
|
@@ -113074,11 +113000,11 @@ function createApp(db2, options) {
|
|
|
113074
113000
|
}));
|
|
113075
113001
|
return app;
|
|
113076
113002
|
}
|
|
113077
|
-
var init_app = __esm(() => {
|
|
113003
|
+
var init_app = __esm(async () => {
|
|
113078
113004
|
init_dist7();
|
|
113079
113005
|
init_cors();
|
|
113080
113006
|
init_logger2();
|
|
113081
|
-
init_auth2();
|
|
113007
|
+
await init_auth2();
|
|
113082
113008
|
});
|
|
113083
113009
|
|
|
113084
113010
|
// ../../node_modules/.bun/@electric-sql+pglite@0.3.16/node_modules/@electric-sql/pglite/dist/chunk-QY3QWFKW.js
|
|
@@ -169207,18 +169133,33 @@ var init_sessions = __esm(async () => {
|
|
|
169207
169133
|
});
|
|
169208
169134
|
|
|
169209
169135
|
// src/routes/platform/games/verify.ts
|
|
169210
|
-
|
|
169136
|
+
function toUserInfo(userData) {
|
|
169137
|
+
return {
|
|
169138
|
+
sub: userData.id,
|
|
169139
|
+
email: userData.email || "",
|
|
169140
|
+
name: userData.name || userData.username || "",
|
|
169141
|
+
email_verified: true,
|
|
169142
|
+
given_name: undefined,
|
|
169143
|
+
family_name: undefined,
|
|
169144
|
+
timeback_id: userData.timebackId || undefined
|
|
169145
|
+
};
|
|
169146
|
+
}
|
|
169147
|
+
var gameVerifyRouter, sandboxAuth2;
|
|
169211
169148
|
var init_verify = __esm(async () => {
|
|
169212
169149
|
init_dist7();
|
|
169213
169150
|
init_errors2();
|
|
169214
169151
|
init_constants();
|
|
169215
169152
|
init_error_handler();
|
|
169216
|
-
await
|
|
169153
|
+
await __promiseAll([
|
|
169154
|
+
init_api3(),
|
|
169155
|
+
init_providers()
|
|
169156
|
+
]);
|
|
169217
169157
|
gameVerifyRouter = new Hono2;
|
|
169158
|
+
sandboxAuth2 = createSandboxAuthProvider();
|
|
169218
169159
|
gameVerifyRouter.post("/verify", async (c2) => {
|
|
169219
169160
|
const body2 = await c2.req.json().catch(() => ({}));
|
|
169220
169161
|
const token = body2?.token;
|
|
169221
|
-
if (!token) {
|
|
169162
|
+
if (typeof token !== "string" || !token) {
|
|
169222
169163
|
return c2.json({ error: "Missing token" }, 400);
|
|
169223
169164
|
}
|
|
169224
169165
|
const demoUser = DEMO_TOKENS[token];
|
|
@@ -169231,49 +169172,25 @@ var init_verify = __esm(async () => {
|
|
|
169231
169172
|
return c2.json({ error: "User not found in sandbox" }, 500);
|
|
169232
169173
|
}
|
|
169233
169174
|
return c2.json({
|
|
169234
|
-
claims: { sandbox: true },
|
|
169175
|
+
claims: { sandbox: true, exp: Math.floor(Date.now() / 1000) + 15 * 60 },
|
|
169235
169176
|
gameId: MOCK_GAME_ID,
|
|
169236
|
-
user:
|
|
169237
|
-
sub: userData.id,
|
|
169238
|
-
email: userData.email || "",
|
|
169239
|
-
name: userData.name || userData.username || "",
|
|
169240
|
-
email_verified: true,
|
|
169241
|
-
given_name: undefined,
|
|
169242
|
-
family_name: undefined,
|
|
169243
|
-
timeback_id: userData.timebackId || undefined
|
|
169244
|
-
}
|
|
169177
|
+
user: toUserInfo(userData)
|
|
169245
169178
|
});
|
|
169246
169179
|
}
|
|
169247
169180
|
if (token.endsWith(".sandbox")) {
|
|
169181
|
+
const payload = await sandboxAuth2.validateGameToken(token);
|
|
169182
|
+
if (!payload) {
|
|
169183
|
+
return c2.json({ error: "Invalid or expired sandbox token" }, 400);
|
|
169184
|
+
}
|
|
169248
169185
|
try {
|
|
169249
|
-
const
|
|
169250
|
-
|
|
169251
|
-
|
|
169252
|
-
|
|
169253
|
-
|
|
169254
|
-
|
|
169255
|
-
const userData = await db2.query.users.findFirst({
|
|
169256
|
-
where: (users3, { eq: eq3 }) => eq3(users3.id, userId)
|
|
169257
|
-
});
|
|
169258
|
-
if (!userData) {
|
|
169259
|
-
return c2.json({ error: "User not found in sandbox" }, 500);
|
|
169260
|
-
}
|
|
169261
|
-
return c2.json({
|
|
169262
|
-
claims: payload,
|
|
169263
|
-
gameId: gameIdOrSlug,
|
|
169264
|
-
user: {
|
|
169265
|
-
sub: userData.id,
|
|
169266
|
-
email: userData.email || "",
|
|
169267
|
-
name: userData.name || userData.username || "",
|
|
169268
|
-
email_verified: true,
|
|
169269
|
-
given_name: undefined,
|
|
169270
|
-
family_name: undefined,
|
|
169271
|
-
timeback_id: userData.timebackId || undefined
|
|
169272
|
-
}
|
|
169273
|
-
});
|
|
169274
|
-
} else {
|
|
169275
|
-
throw new Error("Malformed sandbox token: expected 3 parts");
|
|
169186
|
+
const db2 = c2.get("db");
|
|
169187
|
+
const userData = await db2.query.users.findFirst({
|
|
169188
|
+
where: (users3, { eq: eq3 }) => eq3(users3.id, payload.uid)
|
|
169189
|
+
});
|
|
169190
|
+
if (!userData) {
|
|
169191
|
+
return c2.json({ error: "User not found in sandbox" }, 500);
|
|
169276
169192
|
}
|
|
169193
|
+
return c2.json({ claims: payload, gameId: payload.sub, user: toUserInfo(userData) });
|
|
169277
169194
|
} catch {
|
|
169278
169195
|
return c2.json({ error: "Invalid sandbox token" }, 400);
|
|
169279
169196
|
}
|
|
@@ -169781,13 +169698,13 @@ var init_server = __esm(async () => {
|
|
|
169781
169698
|
init_port();
|
|
169782
169699
|
init_package();
|
|
169783
169700
|
init_config();
|
|
169784
|
-
init_app();
|
|
169785
169701
|
init_database2();
|
|
169786
169702
|
init_options();
|
|
169787
169703
|
await __promiseAll([
|
|
169788
169704
|
init_api3(),
|
|
169789
169705
|
init_context(),
|
|
169790
|
-
init_providers()
|
|
169706
|
+
init_providers(),
|
|
169707
|
+
init_app()
|
|
169791
169708
|
]);
|
|
169792
169709
|
version5 = package_default.version;
|
|
169793
169710
|
});
|