# Auth

Who is calling: guest sessions, `ctx.auth` guards, Google sign-in from the client, and what a user ID means in Fold.

## The three states

Every caller is one of these:

| State | `userId` | `isGuest` | `isSignedIn` | `provider` |
|---|---|---|---|---|
| Signed out | `null` | `false` | `false` | `null` |
| Guest | a string | `true` | `false` | `"guest"` |
| Signed in with Google | a string | `false` | `true` | `"google"` |

`displayName` is always a string. Signed-in users may also have `email`, `emailVerified`, and `picture`, if they agreed to share them.

## Guests

A new visitor gets a guest session automatically. It is an HTTP-only cookie for that app, so a guest's data is private to that browser without any setup. Guest access ends if the browser's data is cleared or the session expires before the guest signs in.

Guests work the same in local dev and hosted. In local dev only, you can act as a named test identity with `?fold_guest=<name>` in the URL or `npx @aozumi/fold auth as <name>` (reset with `npx @aozumi/fold auth reset`). Named identities cannot be upgraded to an account. Hosted folds ignore `?fold_guest=`.

## ctx.auth on the server

```ts
query(async (ctx) => {
  const { userId } = ctx.auth.requireIdentity();
  return ctx.db.notes.withIndex("by_owner", (q) => q.eq("ownerId", userId)).collect();
})
```

| Member | Use |
|---|---|
| `ctx.auth.userId` | `string \| null` |
| `ctx.auth.isGuest`, `ctx.auth.isSignedIn`, `ctx.auth.isAuthenticated` | Separate flags. Neither `isGuest` nor `isSignedIn` is true without a session. |
| `ctx.auth.requireIdentity()` | Returns the guest or signed-in identity. Throws `A guest session or sign-in is required.` when signed out. Use it for data that belongs to a person. |
| `ctx.auth.requireSignedIn()` | Returns the signed-in identity. Throws `Sign in is required.` for guests. Use it for account-only actions. |

Rules:

- Declare owner fields with `userId()`, not `string()`.
- Filter reads by the caller's `userId`, and load and compare the owner again before every update or delete.
- A user ID is not a credential. Do not accept user IDs from the client as proof of who is calling. Do not create your own guest IDs or check ID prefixes.
- Shared data should be shared on purpose, through a query that does not filter by user. Do not invent a fake global user.

## Require sign-in for the whole app

```ts
export default fold({
  auth: { requireSignIn: true },
  // ...
});
```

With `requireSignIn: true`, no query, mutation, action, or endpoint runs until the caller signs in with Google. The page and the sign-in button still load. Client-side checks alone do not protect data; this setting does.

## Google sign-in on the client

```tsx
import { canAccessApp, retryAuth, SignInWithGoogle, signOut, useAuth } from "@aozumi/fold/client";

export function AccountBar() {
  const auth = useAuth();
  if (auth.isLoading) {
    return <p>Checking session</p>;
  }
  if (auth.error) {
    return (
      <div>
        <p role="alert">{auth.error}</p>
        <button type="button" onClick={() => void retryAuth()}>Retry</button>
        <SignInWithGoogle />
      </div>
    );
  }
  return auth.isSignedIn ? (
    <button type="button" onClick={() => void signOut()}>Sign out {auth.displayName}</button>
  ) : (
    <SignInWithGoogle className="border px-3 py-1" />
  );
}
```

| Export | What it does |
|---|---|
| `useAuth()` | Current auth state: the fields above plus `isLoading`, `requireSignIn`, and `error?`. Check `isLoading` before showing signed-out UI. |
| `SignInWithGoogle` | A button that starts Google sign-in. Accepts `className`, `children`, `returnTo`, `disabled`, and other button props. |
| `signInWithGoogle(options?)` | Starts sign-in from your own button. Navigates the page to the sign-in service. |
| `signOut()` | Ends the session. If sign-in is not required, the browser gets a fresh guest session. |
| `getIdentity()` | `{ userId, token?, expired? }`. `token` is the Google identity token, for calls to your own endpoints. |
| `retryAuth()` | Retries a session check that failed. |
| `canAccessApp()` | `true` when the session is ready, has no error, and meets `requireSignIn`. Gate data components on it. |

Sign-in uses the AOZUMI sign-in service at `auth.aozumi.dev`, with Google as the identity provider. It works from `http://localhost` and `http://127.0.0.1` in local dev and from `https://*.aozumi.site` when hosted. The first time a person signs in to an app, they are asked whether to share their name and email with that app. Pass `requestPii: false` (or `requestProfile: false`) to `signInWithGoogle` to ask for no profile data.

## Per-app user IDs

A signed-in person gets a different `userId` in each app. Two folds cannot match up the same person by user ID. Within one app the ID stays the same across sign-ins and redeploys. Email, name, and picture are profile data that can change. Do not use them as keys.

## Guest upgrade

When a guest signs in, Fold moves the guest's data to the account in one transaction:

- Every value in a `userId()` field that equals the guest's ID is changed to the account's ID.
- Plain strings, profile text, and data in other systems are not changed.

If the account already has data, both sets end up under the account. For app-specific merge rules, add a hook. It runs before the automatic transfer, in the same transaction:

```ts
export default fold({
  auth: {
    onGuestUpgrade: async (ctx, { guestUserId, userId }) => {
      ctx.log.info("guest signed in", { guestUserId, userId });
    }
  },
  // ...
});
```

## Calling your own endpoints

From the app's own pages, `fetch` to a same-origin endpoint sends the guest cookie automatically. For a signed-in user, also send the identity token:

```ts
import { getIdentity } from "@aozumi/fold/client";

const token = getIdentity().token;
const response = await fetch("api/notes", {
  method: "POST",
  headers: { "Content-Type": "application/json", ...(token ? { "X-Fold-Token": token } : {}) },
  body: JSON.stringify({ body: "hello" })
});
```

Fold reads identity from `X-Fold-Token`, not from `Authorization`. `Authorization` is passed to your endpoint untouched, for your own use. Guest cookies are only accepted on same-origin requests.

## What works hosted

| Feature | Local dev | Hosted, anonymous |
|---|---|---|
| Guest sessions | Yes | Yes |
| `requireIdentity()`, `requireSignedIn()` | Yes | Yes |
| Google sign-in | Yes | Yes |
| `requireSignIn: true` | Yes | Yes |
| Guest upgrade and `onGuestUpgrade` | Yes | Yes |
| `?fold_guest=` and `auth as` test identities | Yes | No |
| Approving or removing users (`users` command) | No | Coming soon |
