Skip to content

Add signed-in account area: submissions, moderation, and developer profiles#283

Merged
admdly merged 15 commits into
mainfrom
feat/account
Jul 26, 2026
Merged

Add signed-in account area: submissions, moderation, and developer profiles#283
admdly merged 15 commits into
mainfrom
feat/account

Conversation

@admdly

@admdly admdly commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Adds OAuth2/OIDC sign-in (delegated to auth.fossbilling.net) with self-contained signed sessions, and a full /account area on top of it.
  • Lets signed-in users manage a personal account profile and a separate developer profile (publisher identity), submit/edit extensions through a moderated queue, and lets moderators review submissions and developer profiles.
  • Adds developer-profile self-service beyond the basics: a public /developer/[id] page, an approval trust badge, an append-only edit-history log moderators can inspect, single-use ownership-transfer links, and a moderator-approved claim flow for linking unowned ("legacy") profiles to an account.
  • All writes to the shared developers/extensions tables go through the FOSSBilling/api repo's /extensions/v2 service (bearer-assertion authenticated); this app never writes to those tables directly, only reads them for public listings.
  • Final commit renames authordeveloper throughout to match the api repo's own v2 rename (routes, schemas, SubmissionPayload.author.developer) — required, not cosmetic, since submissions and developer-profile actions would otherwise 404/422 against the live api.

See the README's "Authentication" section for the full ownership/moderation model.

Test plan

  • npm run check and npm run format:check (both pass locally)
  • Sign in → create a developer profile → submit a new extension → moderator approves → extension appears in the public directory
  • Edit an approved developer profile → approval badge clears until re-reviewed
  • Generate a transfer link on /account/developer → accept it from a second account → ownership moves, badge clears
  • Claim an unowned/legacy developer profile from its public page → moderator approves/rejects at /account/moderate/developers/claims
  • View a developer profile's edit history at /account/moderate/developers/[id]/history

admdly added 9 commits July 21, 2026 18:27
Mirrors the FOSSBilling/api repo's own v2 rename (authors -> developers
table, /authors/* -> /developers/* routes, SubmissionPayload.author ->
.developer): "author" implied solo/literary authorship, which never fit
an entity that can be an organization, gets moderated, and can be
transferred or claimed. Required, not cosmetic — submissions and every
developer-profile action would 404/422 against the live api otherwise.

extensions.author_id (the v1-owned FK column) is deliberately left
unrenamed, matching the api repo's own v1 boundary; database.ts aliases
it to developer_id immediately so nothing downstream depends on the old
name.

Claude-Session: https://claude.ai/code/session_01GP7BWksJf2KBFc2RGirEyN
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jul 26, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
extensions c3165c1 Commit Preview URL

Branch Preview URL
Jul 26 2026, 08:41 PM

@admdly admdly self-assigned this Jul 26, 2026
@admdly
admdly marked this pull request as draft July 26, 2026 18:22

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

All reported issues were addressed

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread src/lib/db/users.sql Outdated
Comment thread src/pages/account/moderate/developers/[id]/history.astro Outdated
Comment thread src/lib/oauth.ts Outdated
Comment thread src/lib/session.ts Outdated
Comment thread src/pages/auth/callback.ts
Comment thread src/pages/developer/[id].astro Outdated
Comment thread src/lib/developer-form.ts Outdated
Comment thread src/pages/auth/login.ts
Comment thread src/layouts/Base.astro Outdated
Comment thread src/types/index.ts Outdated
admdly added 3 commits July 26, 2026 19:48
Addresses the cubic-dev-ai review on #283 (20 findings):

Security:
- oauth.ts: isSafeRedirectPath rejected absolute/protocol-relative URLs
  and backslashes but not tab/CR/LF, which URL parsers strip — "/\t/evil.com"
  passed the check but resolved to "//evil.com" (protocol-relative) once a
  browser followed the redirect. Reject those characters too.
- Developer-supplied URLs (Developer.URL, DeveloperHistoryEntry.URL) were
  rendered as <a href> unchecked; a javascript: URI would execute in
  whoever clicked it, including a moderator reviewing profile history.
  Added isSafeHttpUrl (src/lib/safe-url.ts) and applied it at all three
  render sites (public developer page, moderator profile list, history).
- logout.ts: POST had no origin check, so a cross-site auto-submitted form
  could force a session to be cleared (Set-Cookie deletion applies
  regardless of whether the request carried the cookie). Reject POSTs with
  a mismatched Origin.
- session.ts: a malformed session cookie made base64url/HMAC verification
  throw uncaught, 500ing every page (including public ones) for that
  visitor instead of treating them as signed out. Wrapped in try/catch,
  matching the JSON.parse handling already in the same function.

Correctness:
- users.ts: callback.ts intentionally doesn't fail sign-in if upsertUser
  fails, which can leave a signed-in user with no `users` row.
  updateUserProfile's UPDATE would then silently affect zero rows and the
  caller would redirect claiming success. Now ensures the row exists
  (INSERT ... ON CONFLICT DO NOTHING) before updating, and refreshes
  updated_at.
- submission-form.ts: adding a release during an edit only required
  version_tag (the other fields are optional in edit mode); filling in
  just the tag sent empty date/download_url/min_fossbilling_version to the
  api. Now validates all four together and raises a SubmissionValidationError
  the calling pages surface as a normal form error.
- account/developer/index.astro: a failed profile save (e.g. duplicate id)
  re-rendered the form from the pre-save `developer`, blanking every field
  on first-time creation. Now falls back to the just-submitted values.
- login.ts: a stale OAUTH_REDIRECT_COOKIE from an abandoned login attempt
  could redirect a later, unrelated login attempt to the old target. Clear
  it explicitly when the new attempt has no (safe) redirect of its own.
- Base.astro: the auth-error banner said "Sign-in with GitHub failed",
  but sign-in is delegated to auth.fossbilling.net's OIDC service, not
  GitHub directly. Generic message instead.
- types/index.ts: DeveloperProfileInput only omitted `approved`, leaving
  `unclaimed` (local-only, api-repo-unaware) assignable into a PUT body.
  Now omits both.
- developer-form.ts: the publisher `type` field was cast from form input
  with no validation against DEVELOPER_TYPES. Now validated, falls back to
  'user' for anything else.

Robustness (5 sites): FormData.get() can return a File for any field name
in a multipart request; several handlers cast straight to `string | null`
and called .trim(), which throws on a File and 500s the request instead of
treating it as invalid input. Added a shared formString() helper
(src/lib/form.ts) and applied it in submission-form.ts, developer-form.ts,
account/profile/index.astro, developer/[id].astro, and both reject.ts
moderation routes.

Migration tooling (the one P1 with no code fix otherwise): db:migrate:local
and db:migrate:remote only ever ran the base CREATE TABLE IF NOT EXISTS,
never the incremental ALTER migrations — exactly the gap that made the
display_name/bio columns missing on both local and remote earlier today.
Adopted wrangler's native D1 migrations (migrations_dir in wrangler.jsonc),
split the schema into 0001_create_users/0002_add_is_moderator/
0003_add_profile_fields, and seeded the d1_migrations bookkeeping table on
both databases to reflect the ALTERs already applied by hand — the shared
D1 database already uses this same mechanism for the api repo's own
migrations, so this just brings this repo's side in line with it.

Not changed: auth/login.ts's redirect_uri uses url.origin, which is
correct for the two actually-registered callback URIs (production,
localhost) but means OAuth can't complete on ad-hoc preview deployments.
Switching to a fixed canonical origin wouldn't actually fix that — the
session would land on production instead of the preview being tested —
so this is left as a known limitation rather than a fake fix.

Claude-Session: https://claude.ai/code/session_01GP7BWksJf2KBFc2RGirEyN
The previous commit's git add included a stale pathspec that made the
whole command abort, so only the migration file renames actually landed.
This is the rest of that same change — see the previous commit message
for the full description of what's being fixed.

Claude-Session: https://claude.ai/code/session_01GP7BWksJf2KBFc2RGirEyN
Lets a developer delete just their publisher profile or their whole
account, gated on having no published extensions to avoid orphaning
them. Requires the api repo's new DELETE /developers/me endpoint.

Claude-Session: https://claude.ai/code/session_01GP7BWksJf2KBFc2RGirEyN
@admdly
admdly marked this pull request as ready for review July 26, 2026 20:09

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

All reported issues were addressed across 49 files

Tip: cubic used a learning from your PR history. Let your coding agent read cubic learnings directly with the cubic MCP.

Re-trigger cubic

Comment thread src/lib/submission-form.ts Outdated
Comment thread src/pages/account/delete.astro
Comment thread src/lib/submission-form.ts Outdated
Comment thread src/lib/submission-form.ts
Comment thread src/types/index.ts
Comment thread src/pages/auth/logout.ts Outdated
Comment thread src/lib/developer-form.ts Outdated
Comment thread src/pages/account/extensions/[id]/edit.astro Outdated
Comment thread src/lib/oauth.ts Outdated
Comment thread src/lib/apiClient.ts Outdated
- requireUser now verifies the users row still exists, closing the gap
  where a session on another device survives account deletion for up
  to 30 days
- edit-mode submissions always use the verified extension id, never
  the form's own extension_id
- extension type/source type are validated against known enums; a new
  (non-edit) submission with no release now fails validation instead
  of publishing empty version/download_url fields
- empty developer id on create is rejected instead of reaching the API
- public developer reads are typed without contact_email so a future
  change can't leak it without a type error
- logout rejects a missing Origin header, not just a mismatched one
- removed the unused offline_access OAuth scope
- apiClient synthesizes a clean ApiRequestError on a non-JSON upstream
  response instead of throwing a raw SyntaxError
- extension edit form preserves submitted values on a rejected edit

Claude-Session: https://claude.ai/code/session_01GP7BWksJf2KBFc2RGirEyN

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

0 issues found across 11 files (changes from recent commits).

Requires human review: PR adds database migrations, authentication/authorization logic, and a new user-facing account area — all changes that require human sign-off for security, data model, and architectural decisions.

Re-trigger cubic

@admdly
admdly merged commit e7c2818 into main Jul 26, 2026
9 checks passed
@admdly
admdly deleted the feat/account branch July 26, 2026 20:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant