scorezilla 0.3.0-next.2 → 0.3.0-next.3

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/index.js CHANGED
@@ -631,7 +631,7 @@ function validateMetadata(metadata) {
631
631
  }
632
632
  var Scorezilla = class _Scorezilla {
633
633
  /** The package version, injected at build time from `package.json`. */
634
- static version = "0.3.0-next.2";
634
+ static version = "0.3.0-next.3";
635
635
  #config;
636
636
  #userAgent;
637
637
  #authHeader;
@@ -816,7 +816,7 @@ function createClient(config) {
816
816
  }
817
817
 
818
818
  // src/index.ts
819
- var SDK_VERSION = "0.3.0-next.2";
819
+ var SDK_VERSION = "0.3.0-next.3";
820
820
 
821
821
  export { SDK_VERSION, Scorezilla, ScorezillaError, createClient, detectRuntime };
822
822
  //# sourceMappingURL=index.js.map
package/dist/server.cjs CHANGED
@@ -708,13 +708,141 @@ function verifyFirebaseIdToken(options) {
708
708
  });
709
709
  }
710
710
 
711
+ // src/github-oauth.ts
712
+ var GITHUB_TOKEN_URL = "https://github.com/login/oauth/access_token";
713
+ var GITHUB_USER_URL = "https://api.github.com/user";
714
+ var MESSAGE_SOURCE = "scorezilla:github-oauth";
715
+ var USER_AGENT = "scorezilla-sdk-github-oauth";
716
+ var STATE_RE = /^[A-Za-z0-9_-]{8,128}$/;
717
+ var CODE_RE = /^[A-Za-z0-9_-]{1,128}$/;
718
+ function createGitHubOAuthHandler(config) {
719
+ if (typeof config.clientId !== "string" || config.clientId.length === 0) {
720
+ throw new TypeError("createGitHubOAuthHandler: clientId is required.");
721
+ }
722
+ if (typeof config.clientSecret !== "string" || config.clientSecret.length === 0) {
723
+ throw new TypeError("createGitHubOAuthHandler: clientSecret is required.");
724
+ }
725
+ let allowedOrigin;
726
+ try {
727
+ const parsed = new URL(config.allowedOrigin);
728
+ allowedOrigin = parsed.origin;
729
+ if (allowedOrigin === "null") throw new Error("opaque origin");
730
+ } catch {
731
+ throw new TypeError(
732
+ "createGitHubOAuthHandler: allowedOrigin must be an absolute origin, e.g. 'https://mygame.example' (got " + JSON.stringify(config.allowedOrigin) + ")."
733
+ );
734
+ }
735
+ const fetchImpl = config.fetch ?? fetch;
736
+ return async (req) => {
737
+ if (req.method !== "GET") {
738
+ return new Response("method not allowed", { status: 405, headers: { allow: "GET" } });
739
+ }
740
+ const url = new URL(req.url);
741
+ const state = url.searchParams.get("state") ?? "";
742
+ const code = url.searchParams.get("code");
743
+ const ghError = url.searchParams.get("error");
744
+ if (!STATE_RE.test(state)) {
745
+ return new Response("invalid or missing state parameter", { status: 400 });
746
+ }
747
+ if (ghError !== null) {
748
+ return callbackPage(
749
+ { state, error: ghError === "access_denied" ? "access_denied" : "exchange_failed" },
750
+ allowedOrigin
751
+ );
752
+ }
753
+ if (code === null || !CODE_RE.test(code)) {
754
+ return new Response("invalid or missing code parameter", { status: 400 });
755
+ }
756
+ let accessToken;
757
+ try {
758
+ const tokenRes = await fetchImpl(GITHUB_TOKEN_URL, {
759
+ method: "POST",
760
+ headers: {
761
+ accept: "application/json",
762
+ "content-type": "application/x-www-form-urlencoded",
763
+ "user-agent": USER_AGENT
764
+ },
765
+ body: new URLSearchParams({
766
+ client_id: config.clientId,
767
+ client_secret: config.clientSecret,
768
+ code
769
+ }).toString()
770
+ });
771
+ const tokenBody = await tokenRes.json();
772
+ if (!tokenRes.ok || typeof tokenBody.access_token !== "string") {
773
+ return callbackPage({ state, error: "exchange_failed" }, allowedOrigin);
774
+ }
775
+ accessToken = tokenBody.access_token;
776
+ } catch {
777
+ return callbackPage({ state, error: "exchange_failed" }, allowedOrigin);
778
+ }
779
+ try {
780
+ const userRes = await fetchImpl(GITHUB_USER_URL, {
781
+ method: "GET",
782
+ headers: {
783
+ authorization: `Bearer ${accessToken}`,
784
+ accept: "application/vnd.github+json",
785
+ "user-agent": USER_AGENT
786
+ }
787
+ });
788
+ if (!userRes.ok) {
789
+ return callbackPage({ state, error: "exchange_failed" }, allowedOrigin);
790
+ }
791
+ const user = await userRes.json();
792
+ if (typeof user.id !== "number" || !Number.isSafeInteger(user.id)) {
793
+ return callbackPage({ state, error: "exchange_failed" }, allowedOrigin);
794
+ }
795
+ return callbackPage({ state, id: String(user.id) }, allowedOrigin);
796
+ } catch {
797
+ return callbackPage({ state, error: "exchange_failed" }, allowedOrigin);
798
+ }
799
+ };
800
+ }
801
+ function callbackPage(payload, allowedOrigin) {
802
+ const message = JSON.stringify({ source: MESSAGE_SOURCE, ...payload });
803
+ const target = JSON.stringify(allowedOrigin);
804
+ const html = `<!doctype html>
805
+ <html>
806
+ <head><meta charset="utf-8"><title>Signing in\u2026</title></head>
807
+ <body>
808
+ <script>
809
+ (function () {
810
+ if (window.opener) {
811
+ window.opener.postMessage(${message}, ${target});
812
+ window.close();
813
+ } else {
814
+ document.body.textContent =
815
+ 'Sign-in handled \u2014 you can close this window. (If this keeps appearing, ' +
816
+ 'the game page may be setting Cross-Origin-Opener-Policy: same-origin, ' +
817
+ 'which severs the popup link; use same-origin-allow-popups.)';
818
+ }
819
+ })();
820
+ </script>
821
+ </body>
822
+ </html>`;
823
+ return new Response(html, {
824
+ status: 200,
825
+ headers: {
826
+ "content-type": "text/html; charset=utf-8",
827
+ // One-shot page embedding a one-shot state — never cache it.
828
+ "cache-control": "no-store",
829
+ // The URL carried the OAuth `code`; nothing on this page may leak it.
830
+ "referrer-policy": "no-referrer",
831
+ // Defense in depth for the inline script: nothing else may load or
832
+ // run on this page even if a future regression reflected input here.
833
+ "content-security-policy": "default-src 'none'; script-src 'unsafe-inline'",
834
+ "x-content-type-options": "nosniff"
835
+ }
836
+ });
837
+ }
838
+
711
839
  // src/server.ts
712
840
  var Scorezilla = class _Scorezilla {
713
841
  /** The package version, injected at build time from `package.json`.
714
842
  * Mirrors the static on the public-key client so consumers can log
715
843
  * the running SDK build the same way regardless of which surface
716
844
  * they imported. */
717
- static version = "0.3.0-next.2";
845
+ static version = "0.3.0-next.3";
718
846
  #keyId;
719
847
  #secret;
720
848
  #baseUrl;
@@ -983,6 +1111,7 @@ function isCorsOriginAllowed(rule, origin) {
983
1111
 
984
1112
  exports.Scorezilla = Scorezilla;
985
1113
  exports.ScorezillaError = ScorezillaError;
1114
+ exports.createGitHubOAuthHandler = createGitHubOAuthHandler;
986
1115
  exports.createScoreSubmitHandler = createScoreSubmitHandler;
987
1116
  exports.verifyAuth0Jwt = verifyAuth0Jwt;
988
1117
  exports.verifyClerkJwt = verifyClerkJwt;