SPEC_ACCOUNTS.md

accounts.siao.ai — spec

What this covers: the design of accounts.siao.ai, the identity provider for every siao.ai application — the account model, sign-in methods, what each application keeps, and how cloning is authorised.

For what is actually built, deployed or reachable, see ClaudeDocs/HANDOFF.md — status deliberately does not live here.

This spec supersedes parts of SPEC_SIAOHUB.md. Specifically: the "Auth" implementation decision (Forgejo OAuth2 as SiaoHub's identity provider), the clone-approval mechanism (Forgejo collaborator), and user story 7 ("a logged-in user can clone any general-visibility repo"). See Superseded decisions at the end of this file. Do not implement SPEC_SIAOHUB.md's auth model — read this one instead.

Problem Statement

The siao.ai domain family is growing into several independent web applications — the homepage, a git frontend at git.siao.ai, an app index and homework/answer platform at apps.siao.ai, and more the owner intends to add later. Today each application owns its own notion of identity: SiaoHub authenticates against Forgejo's OAuth2 provider, while AnswerHub keeps its own user table with passwords, email verification, 2FA and magic links. The same person is two unrelated accounts with two unrelated sessions, and every new application would add a third, a fourth, and so on.

The owner wants the model every large service uses — one account that works across everything, the way a single Google account covers mail, photos, storage and developer tools. Three specific forces make this urgent rather than aspirational:

  1. Passkeys are bound to a domain. The owner wants passkeys as a primary sign-in method. A passkey registered at one subdomain does not work at another, so per-application authentication would force a user to enrol a separate passkey per application. Passkeys are effectively only workable behind a central identity provider.
  2. There are no real users yet. The AnswerHub database holds two rows, both the owner's own test accounts. Unifying identity now costs no migration, no account merging, and no user-facing disruption. After the platform opens and collects real submissions tied to real accounts, the same change becomes an order of magnitude more expensive.
  3. Application count is going up, not down. Each application that ships before unification is another one that has to be converted later.

Secondary problem: the current SiaoHub design uses Forgejo as its identity provider, which requires exposing Forgejo's own login page and OAuth2 endpoints to the public internet. That directly contradicts the project's central design commitment — Forgejo is a hidden backend and no part of its own UI is ever shown to a visitor.

Solution

Stand up accounts.siao.ai, a self-hosted Logto instance that becomes the single identity provider for the entire siao.ai family. Every application — AnswerHub, SiaoHub, and anything added later — becomes an OIDC client of it and stops owning identity entirely.

Identity is deliberately narrow. accounts.siao.ai answers exactly one question: who is this person? It does not answer what may they do — roles, bans, member numbers and per-repo clone approvals stay in the applications that mean something by them.

The account model:

Forgejo does not participate. It is never exposed publicly, never an identity provider, and never an OAuth2 client. SiaoHub continues to talk to it over the internal network with a backend token.

Because Forgejo stays hidden, git clone can no longer be served by Forgejo directly. SiaoHub gains a read-only git smart-HTTP proxy, so an approved user clones from git.siao.ai while Forgejo remains unreachable from outside the host.

Registration is open, but registration is not clone permission. Anyone may create a siao.ai account and use the applications; cloning any repository additionally requires the owner's explicit approval, granted out-of-band over SSH, exactly as before.

User Stories

Account holder — signing up and signing in

  1. As a visitor, I want to create one siao.ai account, so that I can use every application under the domain without registering again.
  2. As a visitor, I want to sign up with my existing Google account, so that I don't have to manage another credential.
  3. As a visitor, I want to sign up with a passkey using my device's biometrics, so that I never have to invent or remember a password.
  4. As a visitor, I want to be certain that no password of mine is stored by this site, so that a breach here cannot expose a credential I reuse elsewhere.
  5. As an account holder, I want to enrol more than one passkey (laptop and phone), so that losing one device does not lock me out.
  6. As an account holder who has lost access to every enrolled method, I want to recover my account through a one-time code sent to my email, so that I can get back in without contacting the owner.
  7. As an account holder, I want that recovery path to be rate-limited, so that it cannot be used as a back door into my account.
  8. As an account holder, I do not want the recovery path presented as a normal sign-in option, so that the primary methods stay primary and the recovery path stays rare.
  9. As an account holder already signed in at one siao.ai application, I want to arrive at another one already signed in, so that it behaves like one product rather than several.
  10. As an account holder, I want signing out to be meaningful across the family, so that leaving a shared computer does not leave a session open somewhere I forgot about.
  11. As an account holder, I want the sign-in page to look like the rest of siao.ai, so that I can tell I am not on a phishing page.
  12. As an account holder, I want my session to survive for weeks, so that routine use does not mean re-authenticating constantly.
  13. As an account holder, I want to review and remove my enrolled passkeys and linked Google account, so that I stay in control of how my account can be accessed.

AnswerHub user

  1. As an AnswerHub user, I want to sign in with my siao.ai account, so that AnswerHub does not need its own credentials.
  2. As an AnswerHub user, I want my submissions, comments and likes to stay attached to me across the identity change, so that nothing I posted is orphaned.
  3. As an AnswerHub user, I want my member number (serialNumber) to remain an AnswerHub concept, so that it means what it has always meant on that platform.
  4. As an AnswerHub administrator, I want role and ban decisions to remain AnswerHub's own, so that suspending someone from the homework platform does not touch their siao.ai identity or their access to unrelated applications.
  5. As an AnswerHub user, I want a first sign-in to create my AnswerHub profile automatically, so that there is no second registration step.

SiaoHub visitor and account holder

  1. As an anonymous visitor, I want to browse general-visibility repos without any account, so that the portfolio is readable by a recruiter or a passer-by who will never sign up.
  2. As an anonymous visitor, I want visibility:preview repos to still show only their first lines, so that the existing preview tier keeps working exactly as before.
  3. As a signed-in account holder, I want to see clearly whether I am approved to clone a given repo, so that I am not guessing.
  4. As a signed-in account holder who is not approved, I want a clear statement that cloning requires the owner's approval, so that I know the gate is deliberate and not a bug.
  5. As an approved account holder, I want to run a normal git clone https://git.siao.ai/<owner>/<repo>.git, so that I get a real repository with history rather than a snapshot.
  6. As an approved account holder, I want git fetch and git pull to keep working afterwards, so that my clone stays useful over time.
  7. As an account holder, I want cloning to require nothing but my siao.ai identity, so that I never need a Forgejo account.
  8. As a visitor, I want git.siao.ai to never show me a Forgejo page, login form or OAuth consent screen, so that the design commitment that Forgejo is invisible actually holds.

Owner

  1. As the owner, I want to sign in to every one of my applications with one account, so that I stop maintaining separate credentials for my own infrastructure.
  2. As the owner, I want to approve a specific person for a specific repository, so that clone access is granted deliberately rather than implied by registration.
  3. As the owner, I want that approval to be possible only over SSH on the host, so that no web request — including one from my own authenticated session — can ever grant clone access.
  4. As the owner, I want the approval list to be version-controlled, so that who was granted what, when and why is answerable from git history without building an audit system.
  5. As the owner, I want granting approval to use the push-to-deploy chain I already have, so that I am not learning a second operational workflow.
  6. As the owner, I want to revoke an approval the same way I granted it, so that revocation is as routine as granting.
  7. As the owner, I want Forgejo to remain unreachable from the public internet after this change, so that the smallest possible surface is exposed.
  8. As the owner, I want to add a future application as an OIDC client with configuration rather than code, so that the marginal cost of application number four is near zero.
  9. As the owner, I want to hold no user passwords anywhere in my infrastructure, so that a password-disclosure incident is not possible.
  10. As the owner, I want the sign-in experience styled with the existing SiaoHub design tokens, so that the family looks like one product without a second design exercise.
  11. As the owner, I want existing sessions to survive a brief identity- provider outage, so that a home-server reboot does not sign everyone out of everything.
  12. As the owner, I want the identity provider to ride the same host, tunnel and deploy chain as everything else, so that I do not maintain a second deployment mechanism.

Implementation Decisions

Identity provider

What lives where

AnswerHub

SiaoHub

Sessions and availability

Ordering

Testing Decisions

A good test here asserts what a visitor, an account holder or the owner can actually observe — a page's content, whether a clone succeeds, what an HTTP endpoint returns — not the internal shape of a session object or the intermediate steps of an authorisation calculation. Existing conventions apply: Vitest for units, Playwright for end-to-end, and a fixture server standing in for external systems, as SiaoHub already does with e2e/fixtures/fake-forgejo-server.mjs.

Seams under test. Three, of which two already exist:

Highest-value unit tests, because a missed branch is a disclosure bug rather than a cosmetic one:

End-to-end tests:

Git proxy tests exercise the real client where possible — an actual git clone against the running service in end-to-end runs, since a mocked HTTP exchange would not catch protocol-level mistakes. Assert specifically that git push is refused.

Not worth testing: Logto's own correctness. It is a dependency, not code under development. What is worth testing is the project's own configuration of it — that password authentication is genuinely unavailable, and that the OTP path is rate-limited — because those are decisions, not vendor behaviour.

Pipeline gating follows SPEC_SIAOHUB.md: unit tests on push, full suite on review, smoke tests against staging, and a final gate before production, with branch protection enforced for the owner too.

Out of Scope

Further Notes

Superseded decisions

These entries in SPEC_SIAOHUB.md no longer apply:

Superseded Replaced by
Auth: Forgejo OAuth2 is SiaoHub's identity provider Logto OIDC at accounts.siao.ai; Forgejo is never an identity provider and is never exposed
Auth: Google is added later as an OAuth2 source inside Forgejo Google is a connector in Logto
User story 7: a logged-in user can clone any general-visibility repo Cloning always requires explicit per-user, per-repo approval; registration grants browsing only
Clone approval by adding a Forgejo collaborator Approval recorded in a version-controlled file, read-only to the application
resolveRepoVisibility(repo, session) Same seam, additionally taking approval state
Clone served by Forgejo Clone served by SiaoHub's read-only git smart-HTTP proxy

Still in force from SPEC_SIAOHUB.md: the visibility-tier model based on Forgejo repo topics, the preview tier's first-ten-lines rule, resolveRepoVisibility as the single authorisation seam, approval being SSH/localhost-only with no web equivalent anywhere, and the deployment and pipeline-gating decisions.