Skip to content

sync: dev to extern-contrib#979

Merged
PythonSmall-Q merged 3 commits into
extern-contribfrom
dev
May 3, 2026
Merged

sync: dev to extern-contrib#979
PythonSmall-Q merged 3 commits into
extern-contribfrom
dev

Conversation

@github-actions
Copy link
Copy Markdown
Contributor

@github-actions github-actions Bot commented Apr 24, 2026

sync-branches: New code has just landed in dev, so let's bring extern-contrib up to speed!

Summary by Sourcery

Add unified SSO-based login support and update the site footer and legal pages.

New Features:

  • Introduce a new SSO login tab in the messages page with OAuth-based authorization and callback handling.
  • Add a navigation link to the unified authentication center from the main site header.
  • Provide dedicated pages for privacy policy, terms of service, and child protection agreement linked from the homepage.

Enhancements:

  • Replace the standalone centered ICP filing notice with an integrated footer section that also links to legal documents.

Summary by cubic

Adds unified SSO login on the Messages page and refreshes legal/navigation. Migrates all API/WebSocket and asset endpoints to xmoj-script.uk and bumps the script to 3.4.6.

  • New Features

    • SSO login tab in messages.html with OAuth (state validation, ExchangeSSOCode exchange, saves session).
    • Homepage updates: header link to the unified auth center, new privacy.html, terms.html, child-protection.html, and an integrated footer with ICP and legal links.
  • Refactors

Written for commit f6b0dcc. Summary will update on new commits.

@sourcery-ai
Copy link
Copy Markdown
Contributor

sourcery-ai Bot commented Apr 24, 2026

Reviewer's Guide

Adds SSO-based login support to the messages page, wires it into initialization and navigation, and introduces new legal/policy and footer content plus a link to the SSO center on the homepage.

Sequence diagram for new SSO-based login flow on messages page

sequenceDiagram
    actor User
    participant Browser
    participant SSOService
    participant BackendAPI

    User->>Browser: Click btn_sso_login
    Browser->>Browser: startSsoLogin()
    Browser->>Browser: generateUuidV4() for state
    Browser->>Browser: Save state to localStorage
    Browser->>SSOService: Redirect to OAUTH_AUTHORIZE_URL

    User->>SSOService: Authenticate and authorize
    SSOService-->>Browser: Redirect to OAUTH_REDIRECT_URI with code and state

    Browser->>Browser: init() (on load)
    Browser->>Browser: handleSsoCallback()
    Browser->>Browser: Read code and state from URL
    Browser->>Browser: Compare state with localStorage
    alt State mismatch
        Browser->>Browser: Clear code and state from URL
        Browser->>User: showToast(统一认证登录失败: state 校验失败)
    else State valid
        Browser->>BackendAPI: POST API_BASE ExchangeSSOCode(Code, State, RedirectURI, ClientID)
        BackendAPI-->>Browser: { Success, Data{Username, SessionID} }
        alt Exchange or data invalid
            Browser->>User: showToast(统一认证登录失败: 错误信息)
        else Exchange success
            Browser->>Browser: Remove saved state from localStorage
            Browser->>Browser: saveSession(Username, SessionID)
            Browser->>Browser: Clear code and state from URL
            Browser->>User: showToast(统一认证登录成功)
            Browser->>Browser: onLoggedIn()
        end
    end
Loading

Flow diagram for handleSsoCallback logic

flowchart TD
    Start["init() on page load"] --> CheckLoggedIn{Already_logged_in?}
    CheckLoggedIn -->|Yes| EndLoggedIn["onLoggedIn()"]
    CheckLoggedIn -->|No| HandleSSO["handleSsoCallback()"]

    HandleSSO --> HasCode{URL_has_code?}
    HasCode -->|No| ShowLogin["Show login screen"]
    HasCode -->|Yes| LoadState["Load state from URL and localStorage"]

    LoadState --> StateValid{state_equals_saved?}
    StateValid -->|No| ClearBadState["Clear code and state from URL"]
    ClearBadState --> ToastStateError["showToast(state 校验失败)"]
    ToastStateError --> ShowLogin

    StateValid -->|Yes| ExchangeCode["exchangeSsoCode(Code, State)"]
    ExchangeCode --> ExchangeOk{Success and Data valid?}

    ExchangeOk -->|No| ToastExchangeError["showToast(统一认证登录失败)"]
    ToastExchangeError --> ShowLogin

    ExchangeOk -->|Yes| SaveSession["saveSession(Username, SessionID)"]
    SaveSession --> ClearParams["Clear code and state from URL"]
    ClearParams --> ToastSuccess["showToast(统一认证登录成功)"]
    ToastSuccess --> EndLoggedIn

    ShowLogin --> EndAnon["Wait for user to choose login method"]
Loading

Flow diagram for new footer and policy navigation on homepage

flowchart TD
    Home["index.html homepage"] --> NavSSO["Navbar link 统一认证中心"]
    Home --> Footer["Footer container"]

    NavSSO -->|Click| OpenSSO["Open https://sso.xmoj-bbs.me in new tab"]

    Footer --> ICPLink["萌ICP备20240425号 link"]
    Footer --> PrivacyLink["privacy.html link"]
    Footer --> TermsLink["terms.html link"]
    Footer --> ChildLink["child-protection.html link"]

    ICPLink -->|Click| OpenICP["Open icp.gov.moe record page"]
    PrivacyLink -->|Click| OpenPrivacy["Open privacy.html"]
    TermsLink -->|Click| OpenTerms["Open terms.html"]
    ChildLink -->|Click| OpenChild["Open child-protection.html"]
Loading

File-Level Changes

Change Details Files
Introduce SSO login tab and OAuth2 authorization-code flow to messages page, including state handling and callback processing.
  • Add new SSO login tab and button alongside existing manual and bookmarklet login flows, and ensure tab switching logic toggles its visibility.
  • Define OAuth-related constants (authorize URL, client ID, scope, redirect URI, local-storage key) for SSO integration.
  • Implement UUID v4 generation helper for CSRF state, with crypto-based and Math.random fallbacks.
  • Implement startSsoLogin to build the authorization URL with query parameters and redirect the browser to the SSO provider.
  • Add exchangeSsoCode helper that POSTs the authorization code and state to the backend ExchangeSSOCode API and returns parsed JSON.
  • Implement handleSsoCallback to validate state from localStorage, call the backend to exchange code for a session, persist the session, clean query params from the URL, and surface success/failure via toasts.
  • Wire SSO button click handler to startSsoLogin and make init boot function async to await handleSsoCallback before falling back to existing session handling.
messages.html
Update homepage navigation and footer to reference SSO center and legal/policy documents, replacing the old centered ICP notice.
  • Add a new navbar link to the external SSO center in the main navigation bar.
  • Replace the legacy centered ICP notice with a Bootstrap container footer that includes the ICP registration info and links to privacy, terms, and child-protection pages.
index.html
Add standalone policy pages for privacy, terms of service, and child protection, using simple Bootstrap-based layouts.
  • Create privacy.html with Chinese-language privacy agreement content, including sections on data collection, usage, storage, sharing, user rights, and updates.
  • Create terms.html with Chinese-language service agreement content, covering scope, service description, user obligations, IP, liability, and changes/termination.
  • Create child-protection.html with Chinese-language child protection agreement content, describing scope, guardian responsibilities, data protection, risk mitigation, guardian rights, and contact.
  • Ensure each new document links back to the homepage and includes basic Bootstrap styling and metadata.
privacy.html
terms.html
child-protection.html

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@cloudflare-workers-and-pages
Copy link
Copy Markdown

cloudflare-workers-and-pages Bot commented Apr 24, 2026

Deploying xmoj-script-dev-channel with  Cloudflare Pages  Cloudflare Pages

Latest commit: f6b0dcc
Status: ✅  Deploy successful!
Preview URL: https://9712c4c6.xmoj-script-dev-channel.pages.dev

View logs

Copy link
Copy Markdown
Contributor

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 2 issues, and left some high level feedback:

  • In handleSsoCallback, when the state check fails you show a toast and strip query params but leave STORAGE_OAUTH_STATE untouched; consider removing the stored state in this branch as well to avoid stale state values blocking future SSO attempts.
  • All OAuth-related values (authorize URL, client id, scope, redirect URI) are hard-coded in messages.html; it may be more maintainable to centralize these into a single config object or injected config so they can be updated without touching UI logic.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `handleSsoCallback`, when the `state` check fails you show a toast and strip query params but leave `STORAGE_OAUTH_STATE` untouched; consider removing the stored state in this branch as well to avoid stale state values blocking future SSO attempts.
- All OAuth-related values (authorize URL, client id, scope, redirect URI) are hard-coded in `messages.html`; it may be more maintainable to centralize these into a single config object or injected config so they can be updated without touching UI logic.

## Individual Comments

### Comment 1
<location path="messages.html" line_range="1012-1021" />
<code_context>
+    return res.json();
+}
+
+async function handleSsoCallback() {
+    var url = new URL(location.href);
+    var code = url.searchParams.get('code');
+    var state = url.searchParams.get('state');
+    if (!code) return false;
+
+    var expectedState = localStorage.getItem(STORAGE_OAUTH_STATE);
+    if (!state || !expectedState || state !== expectedState) {
+        showToast('统一认证登录失败:state 校验失败', 'danger');
+        url.searchParams.delete('code');
+        url.searchParams.delete('state');
+        history.replaceState(null, '', url.pathname + (url.searchParams.toString() ? '?' + url.searchParams.toString() : ''));
+        return false;
+    }
</code_context>
<issue_to_address>
**suggestion (bug_risk):** Cleaning the URL after SSO may drop the hash fragment and duplicates URL-rewrite logic across success and failure paths.

Both the state-mismatch and success branches call `history.replaceState` with `url.pathname + ...` but omit `url.hash`, which will drop any existing fragment (e.g. `#anchor`) that other code may rely on. The URL-rewrite logic that removes `code`/`state` and rebuilds the URL is also duplicated.

Consider extracting a helper like `clearSsoParams(url)` that:
- Removes `code` and `state` from `url.searchParams`.
- Rebuilds the URL including remaining query parameters and `url.hash`.

Then call it from both branches to avoid duplication and preserve consistent URL behavior.

Suggested implementation:

```
function clearSsoParams(url) {
    url.searchParams.delete('code');
    url.searchParams.delete('state');

    var search = url.searchParams.toString();
    var newUrl = url.pathname + (search ? '?' + search : '') + (url.hash || '');
    history.replaceState(null, '', newUrl);
}

async function handleSsoCallback() {

```

```
    var url = new URL(location.href);
    var code = url.searchParams.get('code');
    var state = url.searchParams.get('state');
    if (!code) return false;

    var expectedState = localStorage.getItem(STORAGE_OAUTH_STATE);
    if (!state || !expectedState || state !== expectedState) {
        showToast('统一认证登录失败:state 校验失败', 'danger');
        clearSsoParams(url);
        return false;
    }

```

Search the rest of `handleSsoCallback` (and any related SSO-flow code) for other places where `code`/`state` are removed and `history.replaceState` is called with `url.pathname + ...` but without `url.hash`. Replace those sequences with `clearSsoParams(url);` to deduplicate the logic and ensure the hash fragment is preserved consistently on all SSO completion paths.
</issue_to_address>

### Comment 2
<location path="messages.html" line_range="967-976" />
<code_context>
     });
 });

+function generateUuidV4() {
+    if (window.crypto && window.crypto.randomUUID) return window.crypto.randomUUID();
+    if (window.crypto && window.crypto.getRandomValues) {
+        var bytes = new Uint8Array(16);
+        window.crypto.getRandomValues(bytes);
+        bytes[6] = (bytes[6] & 0x0f) | 0x40;
+        bytes[8] = (bytes[8] & 0x3f) | 0x80;
+        var hex = Array.from(bytes).map(function(b) { return b.toString(16).padStart(2, '0'); }).join('');
+        return [hex.slice(0, 8), hex.slice(8, 12), hex.slice(12, 16), hex.slice(16, 20), hex.slice(20)].join('-');
+    }
+    return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
+        var r = Math.random() * 16 | 0;
+        var v = c === 'x' ? r : (r & 0x3 | 0x8);
+        return v.toString(16);
+    });
</code_context>
<issue_to_address>
**🚨 suggestion (security):** The `Math.random` fallback for `generateUuidV4` weakens the entropy of the OAuth `state` parameter on older browsers.

Using `crypto.randomUUID`/`getRandomValues` is appropriate, but falling back to `Math.random` for an OAuth `state` value weakens CSRF protection in older environments. For those environments, consider either treating missing `window.crypto` as unsupported (and failing with an error) or requiring at least `crypto.getRandomValues` and otherwise not proceeding, to avoid low‑entropy, more predictable tokens.

Suggested implementation:

```
function generateUuidV4() {
    if (window.crypto && typeof window.crypto.randomUUID === 'function') {
        return window.crypto.randomUUID();
    }

    if (window.crypto && typeof window.crypto.getRandomValues === 'function') {
        var bytes = new Uint8Array(16);
        window.crypto.getRandomValues(bytes);

        // Per RFC 4122 section 4.4: set version to 4 and variant to RFC 4122
        bytes[6] = (bytes[6] & 0x0f) | 0x40;
        bytes[8] = (bytes[8] & 0x3f) | 0x80;

        var hex = Array.from(bytes)
            .map(function(b) { return b.toString(16).padStart(2, '0'); })
            .join('');

        return [
            hex.slice(0, 8),
            hex.slice(8, 12),
            hex.slice(12, 16),
            hex.slice(16, 20),
            hex.slice(20)
        ].join('-');
    }

    // Do not fall back to Math.random for security-sensitive values like OAuth state
    throw new Error('Secure random number generator not available; cannot generate secure UUID v4.');
}

```

If `generateUuidV4()` is used during OAuth flow (e.g., to generate the `state` parameter), callers should be prepared for it to throw in older/unsupported browsers. For a better UX, you may want to catch this error where `generateUuidV4()` is called and display a user-friendly message indicating that the browser is not supported for secure login.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread messages.html
Comment thread messages.html
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

3 issues found across 5 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="child-protection.html">

<violation number="1" location="child-protection.html:8">
P2: External CDN stylesheet is loaded without SRI, which weakens frontend supply-chain integrity protection.</violation>
</file>

<file name="messages.html">

<violation number="1" location="messages.html:977">
P1: The `Math.random` fallback produces predictable output (only ~52 bits of entropy) and is explicitly listed as insecure for OAuth `state` generation. Since `state` is the sole CSRF defense for the OAuth flow, this fallback should either require `crypto.getRandomValues` (supported since IE 11) or throw an error rather than silently downgrading to a weak source. Browsers lacking `window.crypto` are ancient enough that failing with a clear message is preferable to weakened CSRF protection.</violation>

<violation number="2" location="messages.html:1044">
P2: The `catch` block in `handleSsoCallback` does not clean the OAuth `code`/`state` query params from the URL or remove the stored OAuth state. If the code exchange fails, refreshing the page retriggers the same failed exchange in a loop. Both other exit paths (state-mismatch and success) already perform this cleanup.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment thread messages.html
Comment thread child-protection.html
Comment thread messages.html
* switch endpoint to api.xmoj-script.uk for everything

Agent-Logs-Url: https://github.com/XMOJ-Script-dev/XMOJ-Script/sessions/3a87c529-2f63-4617-8be5-5b9f3c33f751

Co-authored-by: PythonSmall-Q <106425289+PythonSmall-Q@users.noreply.github.com>

* Update ASSET_BASE URL to use assets subdomain

Signed-off-by: Shan Wenxiao <seanoj_noreply@yeah.net>

* 3.4.6

* Update version info to 3.4.6

* Update image upload URLs to new asset location

Signed-off-by: Shan Wenxiao <seanoj_noreply@yeah.net>

* Update time and description of 3.4.6

* Update ServerURL for script debugging

Signed-off-by: Shan Wenxiao <seanoj_noreply@yeah.net>

* Update time and description of 3.4.6

* Update SSO button for development version

Signed-off-by: Shan Wenxiao <seanoj_noreply@yeah.net>

* Update time and description of 3.4.6

* Remove badge from messages link in navigation

Signed-off-by: Shan Wenxiao <seanoj_noreply@yeah.net>

* Update time and description of 3.4.6

---------

Signed-off-by: Shan Wenxiao <seanoj_noreply@yeah.net>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: PythonSmall-Q <106425289+PythonSmall-Q@users.noreply.github.com>
Co-authored-by: Shan Wenxiao <seanoj_noreply@yeah.net>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
@hendragon-bot hendragon-bot Bot added user-script This issue or pull request is related to the main user script website This issue or pull request is related to website related files labels May 3, 2026
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 5 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="messages.html">

<violation number="1" location="messages.html:77">
P2: The new SSO tab key (`sso-develop`) does not match the tab router (`sso`), so clicking it hides all login panels and breaks the SSO login UI.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment thread messages.html
@PythonSmall-Q PythonSmall-Q merged commit f1ca323 into extern-contrib May 3, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/L user-script This issue or pull request is related to the main user script website This issue or pull request is related to website related files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants