-- 0002_oauth_tables.sql
-- OAuth 2.1 authorization-server state per design.md §4.2.
--
-- Storage rules:
--   * Broker tokens (access, refresh) are stored as hex-encoded SHA-256 hashes.
--     Plaintext leaves the broker exactly once, when handed to the MCP client.
--   * Forgejo tokens are stored cleartext — the broker has to be able to use
--     them to spawn forgejo-mcp subprocesses. The SQLite file is therefore a
--     sensitive secret at rest; protect it via volume permissions and (in
--     production) an encrypted underlying volume.
--   * Timestamps are unix epoch seconds (INTEGER). Comparing deadlines as
--     ints is dramatically simpler than parsing ISO-8601 strings, and SQLite's
--     type affinity makes mixed-type rows easy to misuse.
--   * No DEFAULT clauses for timestamps — the application supplies them so a
--     test clock can be injected later (phase 5c).

CREATE TABLE clients (
    client_id      TEXT    PRIMARY KEY,
    client_secret  TEXT,                 -- NULL for public clients (PKCE-only)
    redirect_uris  TEXT    NOT NULL,     -- JSON array of strings
    metadata_json  TEXT,                 -- raw RFC 7591 client-metadata blob, optional
    created_at     INTEGER NOT NULL,
    last_used      INTEGER               -- bumped on successful authorize/token use
);

CREATE INDEX idx_clients_last_used ON clients(last_used);

CREATE TABLE auth_codes (
    code                      TEXT    PRIMARY KEY,
    client_id                 TEXT    NOT NULL REFERENCES clients(client_id) ON DELETE CASCADE,
    redirect_uri              TEXT    NOT NULL,
    code_challenge            TEXT    NOT NULL,
    code_challenge_method     TEXT    NOT NULL,    -- only 'S256' accepted by the AS
    scopes                    TEXT    NOT NULL,    -- space-separated
    forgejo_access_token      TEXT    NOT NULL,
    forgejo_refresh_token     TEXT,
    forgejo_token_expires_at  INTEGER NOT NULL,
    forgejo_user_id           INTEGER NOT NULL,
    forgejo_username          TEXT    NOT NULL,
    expires_at                INTEGER NOT NULL,    -- short TTL (~600s); reaper sweeps expired rows
    used_at                   INTEGER              -- non-NULL after exchange; replay defense
);

CREATE INDEX idx_auth_codes_expires_at ON auth_codes(expires_at);

CREATE TABLE access_tokens (
    token_hash                TEXT    PRIMARY KEY,    -- hex(sha256(plaintext_token))
    client_id                 TEXT    NOT NULL REFERENCES clients(client_id) ON DELETE CASCADE,
    forgejo_user_id           INTEGER NOT NULL,
    forgejo_username          TEXT    NOT NULL,
    scopes                    TEXT    NOT NULL,
    forgejo_access_token      TEXT    NOT NULL,
    forgejo_refresh_token     TEXT,
    forgejo_token_expires_at  INTEGER NOT NULL,
    expires_at                INTEGER NOT NULL,
    created_at                INTEGER NOT NULL,
    revoked_at                INTEGER                  -- NULL = active
);

CREATE INDEX idx_access_tokens_expires_at  ON access_tokens(expires_at);
CREATE INDEX idx_access_tokens_forgejo_uid ON access_tokens(forgejo_user_id);

CREATE TABLE refresh_tokens (
    token_hash         TEXT    PRIMARY KEY,            -- hex(sha256(plaintext_token))
    access_token_hash  TEXT    NOT NULL REFERENCES access_tokens(token_hash) ON DELETE CASCADE,
    client_id          TEXT    NOT NULL REFERENCES clients(client_id) ON DELETE CASCADE,
    expires_at         INTEGER NOT NULL,
    created_at         INTEGER NOT NULL,
    revoked_at         INTEGER
);

CREATE INDEX idx_refresh_tokens_expires_at ON refresh_tokens(expires_at);

INSERT INTO broker_meta (key, value) VALUES ('oauth_schema_version', '1');
