feat: native sign-in in the iPhone app: Apple, Google, GitHub, password and emailed code by gnapse · Pull Request #146 · klipstack/klipstack · GitHub
Skip to content

feat: native sign-in in the iPhone app: Apple, Google, GitHub, password and emailed code - #146

Merged
gnapse merged 5 commits into
mainfrom
native-sign-in
Oct 20, 2026
Merged

gnapse merged 5 commits into
mainfrom
native-sign-in

Conversation

@gnapse

@gnapse gnapse commented Oct 20, 2026

Copy link
Copy Markdown
Contributor

Why

The iPhone app signed in through the web's pages in a browser sheet, which can't offer Apple, Google or GitHub: a trip to a provider loses the app's request. App Review 4.8 needs Sign in with Apple wherever Google or GitHub is offered, and Apple sign-ins must be revoked when the account is deleted.

What changes

  • The app has its own sign-in screen with these ways in:

    • Sign in with Apple in iOS's sheet;
    • Google and GitHub in a private sheet (/app/sign-in/<provider>);
    • email with a password, or a 6-digit emailed code.

    Each one ends in the usual klipstack-mobile code and PKCE, so tokens, refresh, the share extension and the widget are unchanged. Consent is skipped for that client.

  • Deleting the account asks "Confirm it's you" on the same form when the server needs a recent sign-in.

  • Apple's refresh token is kept, encrypted with BETTER_AUTH_SECRET, and revoked at Apple when the account is deleted or Apple is disconnected. This is best effort: a failure is logged and the deletion goes on.

  • The app creates no account, in any SIGNUP_MODE.

  • Emailed codes are hashed and allow 3 tries in 10 minutes. At most 10 can be sent a day per address and 100 per IP.

  • Self-hosting docs: the .p8 one-line command is now perl instead of awk.

Verification

  • In the simulator (iPhone 17 Pro, iOS 26.3) against the dev server:

    • A wrong password is refused.
    • The right password reaches Home; the database showed rotating klipstack-mobile refresh tokens.
    • Emailed codes are sent (signing in with one is covered by the tests).
    • Delete after 31 minutes: the "Confirm it's you" sheet opens, signing in there deletes the account, and the app is back at the sign-in screen.
  • New tests cover /app/sign-in*, Apple tokens and revocation, and unlinking in Settings. Each new behavior's test was seen failing without it.

  • pnpm verify and pnpm ci:local pass. In e2e, 305 tests passed and 3 were flaky (feeds, Getting started, highlight resizing) and passed on retry. The image build first failed because the operator CLI now reaches the Apple revocation. It loads that module lazily now, and the image copies it. I checked that the module loads in the image.

  • The review found, and this branch fixes:

    • app sign-ins created accounts when sign-up is open;
    • Apple's authorization was left behind when it signed in no account;
    • the code limit allowed about 27% a year to guess;
    • the delete sheet hid a late error;
    • a rerun of the tests hit a rate limit.

    Dismissed: the mobile app has no unit tests (CI skips it); revoking happens before the delete transaction (documented, best effort).

  • Not verified: real Apple, Google and GitHub sign-ins and Apple's revocation (they need a signed build), and the share extension after a native sign-in.

Risk and follow-ups

  • Apple from the app needs the Sign in with Apple key's primary App ID to be com.klipstack.mobile. Otherwise it answers "Apple's sign-in couldn't be checked".
  • On an http server, any app that claims com.klipstack.app: can get a signed-in Safari reader's code without a prompt. A saved consent allowed this before too; it is documented in docs/security.md.
  • Ten code requests for someone's address stop their emailed codes for a day. Their other ways in still work.
  • Sign-up from the app is left for later.

gnapse and others added 5 commits October 20, 2026 09:56
…ode, Apple, Google and GitHub

Every way in signs in through better-auth in process and answers an authorization code for
klipstack-mobile, which skips consent now. Apple's refresh token is kept, encrypted, to revoke
Sign in with Apple when the account is deleted or Apple disconnected. /sign-in/social is closed
over HTTP. Also replaces the awk command for the .p8 key in the self-hosting docs.

Amp-Thread-ID: https://ampcode.com/threads/T-01a15729-a41b-749a-be6e-639752903e39
Co-authored-by: Amp <amp@ampcode.com>
Apple through iOS's sheet, Google and GitHub in a private sheet, password
or an emailed code, all ending in klipstack-mobile tokens. Deleting the
account asks to confirm it's you on the same screen.

Amp-Thread-ID: https://ampcode.com/threads/T-01a15729-a41b-749a-be6e-639752903e39
Co-authored-by: Amp <amp@ampcode.com>
…nt on http

From the review: the app's Apple and provider sign-ins refuse to create
users whatever SIGNUP_MODE says; consent is skipped only on https, where
only our app opens the redirect; a day's cap on emailed codes; Apple's
authorization is revoked when it signed no one in; the delete sheet keeps
its error.

Amp-Thread-ID: https://ampcode.com/threads/T-01a15729-a41b-749a-be6e-639752903e39
Co-authored-by: Amp <amp@ampcode.com>
… one in

A consent given in process is saved, so asking on http protected nothing
after the first sign-in: the scheme's exposure on http is documented
instead. Apple's fresh token is revoked only for no account or one not
connected to Apple, not on other link errors.

Amp-Thread-ID: https://ampcode.com/threads/T-01a15729-a41b-749a-be6e-639752903e39
Co-authored-by: Amp <amp@ampcode.com>
account.server.ts loads it when deleting, by a relative path: the image
check runs the CLI without the server's environment.

Amp-Thread-ID: https://ampcode.com/threads/T-01a15729-a41b-749a-be6e-639752903e39
Co-authored-by: Amp <amp@ampcode.com>
@gnapse
gnapse enabled auto-merge (squash) October 20, 2026 13:44
@gnapse
gnapse merged commit 5637150 into main Oct 20, 2026
10 checks passed
@gnapse
gnapse deleted the native-sign-in branch October 20, 2026 13:51
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