diff --git a/app/(app)/developers/apps/new/actions.ts b/app/(app)/developers/apps/new/actions.ts index a798d44..8d99216 100644 --- a/app/(app)/developers/apps/new/actions.ts +++ b/app/(app)/developers/apps/new/actions.ts @@ -33,6 +33,11 @@ export async function createAppAction(formData: FormData) { const clientId = `app_${randomToken(16)}`; const clientSecret = `sec_${randomToken(32)}`; + // The api key (activation/bearer API) and the OAuth client secret guard + // different surfaces, so they are independent secrets - leaking one must not + // authenticate the other. The api key satisfies the not-null column here; it + // is the request-bearer flow that issues an api key to the user. + const apiKey = `sec_${randomToken(32)}`; const slug = name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, "") + "-" + randomToken(4); const row = await queryOne<{ id: string }>( @@ -51,11 +56,12 @@ export async function createAppAction(formData: FormData) { oauth_profile_version, status, owner_user_id - ) values ($1, $2, $3, $4, $4, $5, 'confidential', 'client_secret_post', $6, $7, true, 'bn-oauth-2026-05', 'active', $8) returning id`, + ) values ($1, $2, $3, $4, $5, $6, 'confidential', 'client_secret_post', $7, $8, true, 'bn-oauth-2026-05', 'active', $9) returning id`, [ clientId, name, slug, + hashToken(apiKey), hashToken(clientSecret), [redirectUri], ["authorization_code", "refresh_token", "client_credentials", "urn:ietf:params:oauth:grant-type:device_code"], diff --git a/app/(info)/docs/page.tsx b/app/(info)/docs/page.tsx new file mode 100644 index 0000000..c052b42 --- /dev/null +++ b/app/(info)/docs/page.tsx @@ -0,0 +1,86 @@ +import type { Metadata } from "next"; +import Link from "next/link"; +import { DocHeader, DocSection, DocList } from "@/components/Doc"; + +export const metadata: Metadata = { + title: "Documentation — bottleneck", + description: + "Get started with bottleneck: Telegram-confirmed sign-in, passkeys, and an OAuth 2.1 / OpenID Connect provider.", +}; + +export default function DocsPage() { + return ( +
+ + + +

+ bottleneck is an account service with Telegram-confirmed two-factor sign-in and passkeys, + and a full OAuth 2.1 / OpenID Connect provider you can use to sign users into your own + applications. +

+
+ + + + + Create an account + {" "} + and confirm it from Telegram to finish sign-up. + , + "Add a passkey under Password & 2FA to sign in without a password.", + "Each sign-in is approved from your linked Telegram, so a stolen password alone is not enough.", + ]} + /> + + + +

+ bottleneck implements OAuth 2.1 and OpenID Connect. Client metadata is published at the + standard discovery endpoint: +

+

+ /.well-known/openid-configuration +

+ +
+ + +

+ For server-to-server access you can request a long-lived API bearer token. Each request is + reviewed before a token is issued. API keys and OAuth client secrets are separate + credentials — keep both confidential and rotate them if exposed. +

+
+ + +

+ Official client libraries are available for Node.js and Go to verify tokens and call the + API. See the SDK directories in the project for usage. +

+
+ + +

+ Browse the{" "} + + FAQ + {" "} + for common questions, or reach{" "} + + support + {" "} + if you are stuck. +

+
+
+ ); +} diff --git a/app/(app)/faq/page.tsx b/app/(info)/faq/page.tsx similarity index 73% rename from app/(app)/faq/page.tsx rename to app/(info)/faq/page.tsx index 5c38d1f..08e47f5 100644 --- a/app/(app)/faq/page.tsx +++ b/app/(info)/faq/page.tsx @@ -1,7 +1,11 @@ +import type { Metadata } from "next"; import { CircleHelp, ShieldCheck, Boxes } from "lucide-react"; import { Section } from "@/components/Section"; -export const dynamic = "force-dynamic"; +export const metadata: Metadata = { + title: "FAQ — bottleneck", + description: "Answers to common questions about bottleneck accounts, security, and OAuth apps.", +}; function QA({ q, a }: { q: string; a: string }) { return ( @@ -23,15 +27,15 @@ export default function FaqPage() {
@@ -53,7 +57,7 @@ export default function FaqPage() {