@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.
Files changed (3) hide show
  1. package/dist/cli.js +62 -145
  2. package/dist/server.js +62 -145
  3. 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.9.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 && scoreDefaultIsZero(item) && feedbackOutcomeIsDeclared(item)) {
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
- if (rule) {
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 * 1000;
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
- if (payload.sub && payload.uid) {
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 parseSandboxToken(token) {
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
- if (token.includes(".")) {
112950
- const sandboxClaims = parseSandboxToken(token);
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", shouldReturn404: false };
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", shouldReturn404: true };
112951
+ return { success: false, error: "User not found or token invalid" };
113021
112952
  }
113022
- let gameId = claims.gameId;
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 init_auth2 = __esm(() => {
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
- var gameVerifyRouter;
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 init_api3();
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 parts2 = token.split(".");
169251
- if (parts2.length === 3) {
169252
- const payload = JSON.parse(atob(parts2[1]));
169253
- const userId = payload.uid;
169254
- const gameIdOrSlug = payload.sub;
169255
- const db2 = c2.get("db");
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.9.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 && scoreDefaultIsZero(item) && feedbackOutcomeIsDeclared(item)) {
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
- if (rule) {
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 * 1000;
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
- if (payload.sub && payload.uid) {
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 parseSandboxToken(token) {
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
- if (token.includes(".")) {
112949
- const sandboxClaims = parseSandboxToken(token);
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", shouldReturn404: false };
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", shouldReturn404: true };
112950
+ return { success: false, error: "User not found or token invalid" };
113020
112951
  }
113021
- let gameId = claims.gameId;
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 init_auth2 = __esm(() => {
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
- var gameVerifyRouter;
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 init_api3();
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 parts2 = token.split(".");
169250
- if (parts2.length === 3) {
169251
- const payload = JSON.parse(atob(parts2[1]));
169252
- const userId = payload.uid;
169253
- const gameIdOrSlug = payload.sub;
169254
- const db2 = c2.get("db");
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
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@playcademy/sandbox",
3
- "version": "0.9.0",
3
+ "version": "0.10.0",
4
4
  "description": "Local development server for Playcademy game development",
5
5
  "type": "module",
6
6
  "exports": {