Skip to content
File

Blob: src/client/pages/privacy.tsx

typescript203 lines
1import type { ReactNode } from "react";
2 
3import { ContactEmail } from "@/client/components/ui/contact-email";
4import { useClientConfig } from "@/client/lib/config";
5 
6const Section = ({ title, children }: { title: string; children: ReactNode }) => (
7 <section className="mt-12">
8 <h2 className="mb-4 font-display text-xl font-semibold text-zinc-100">{title}</h2>
9 <div className="space-y-4 text-base leading-[1.7] text-zinc-300">{children}</div>
10 </section>
11);
12 
13export const PrivacyPage = () => {
14 const config = useClientConfig();
15 if (!config || config.status !== "ok") return null;
16 const { operatorName, operatorContactEmail } = config;
17 
18 return (
19 <div className="mx-auto max-w-2xl py-12 sm:py-20">
20 <p className="mb-6 text-[11px] font-medium uppercase tracking-[0.18em] text-zinc-500">Privacy policy</p>
21
22 <h1 className="mb-6 font-display text-[clamp(2rem,5.5vw,3.25rem)] font-semibold leading-[1.05] tracking-tight text-zinc-100">
23 Privacy policy
24 </h1>
25
26 <p className="mb-10 text-sm text-zinc-500">Effective April 27, 2026.</p>
27
28 <p className="max-w-[60ch] text-base leading-[1.7] text-zinc-300">
29 tessera is a single sign-on provider operated by {operatorName}. This policy explains what personal information
30 tessera handles, why, and the choices you have. It applies to the service hosted at this domain and to any
31 tessera deployment operated by {operatorName}.
32 </p>
33
34 <Section title="Invite-only by design">
35 <p>
36 tessera does not offer open registration. You can use the service only after an administrator has minted an
37 invite for your email address and you have accepted it. Signing in with Google or GitHub does not create a new
38 account; it works only after you have already signed in with email and password and explicitly linked that
39 identity from your account page. If a Google or GitHub sign-in does not match an existing tessera account, the
40 sign-in is rejected and no profile data is retained.
41 </p>
42 </Section>
43
44 <Section title="What we collect">
45 <p>
46 <strong className="text-zinc-100">Account information.</strong> Your email address, display name, and a
47 slug-safe handle derived from your name at account creation. If you set a password, we store an Argon2id hash
48 of it; we never store the password itself.
49 </p>
50 <p>
51 <strong className="text-zinc-100">Linked identities.</strong> If you link a Google or GitHub identity from
52 your account page, we receive and store the provider's account identifier, your email, and your name as
53 returned by the provider. We also receive access and refresh tokens from the provider; these are encrypted
54 with a server secret before being written to our database.
55 </p>
56 <p>
57 <strong className="text-zinc-100">Sessions.</strong> When you sign in we set a session cookie scoped to this
58 domain. The corresponding session row in our database records the session identifier, an expiry, and the IP
59 address and user-agent that started the session.
60 </p>
61 <p>
62 <strong className="text-zinc-100">Operational data.</strong> Our hosting provider, Cloudflare, sees the IP
63 address of every request and may retain short-lived edge logs for abuse prevention and rate limiting. Sign-in
64 and invite acceptance flows are protected by Cloudflare Turnstile, which evaluates a one-time challenge token;
65 we do not retain the raw challenge response.
66 </p>
67 <p>tessera does not run third-party analytics, advertising, or tracking scripts.</p>
68 </Section>
69
70 <Section title="How we use it">
71 <p>
72 We use the information above only to operate tessera: to authenticate you, to issue OpenID Connect identity
73 tokens to applications you choose to sign in to, to enforce rate limits and abuse protections, and to let
74 administrators manage invites, clients, and user roles.
75 </p>
76 <p>
77 Information that Google returns to tessera through the Google sign-in flow is used solely to authenticate you
78 to your existing tessera account. We do not use Google profile data to build advertising profiles, to train
79 machine-learning models, or for any purpose unrelated to the authentication you initiated. Tessera's use of
80 information received from Google APIs adheres to the{" "}
81 <a
82 href="https://developers.google.com/terms/api-services-user-data-policy"
83 target="_blank"
84 rel="noopener noreferrer"
85 className="text-zinc-100 underline decoration-zinc-700 underline-offset-[6px] transition-colors hover:text-accent-400 hover:decoration-accent-500/60"
86 >
87 Google API Services User Data Policy
88 </a>
89 , including the Limited Use requirements.
90 </p>
91 </Section>
92
93 <Section title="Sub-processors">
94 <p>tessera relies on a small number of providers to operate the service:</p>
95 <ul className="list-disc space-y-2 pl-6">
96 <li>
97 <strong className="text-zinc-100">Cloudflare</strong> hosts the application (Workers, D1, Turnstile,
98 rate-limiting). Data is stored in Cloudflare's infrastructure and may transit through edge locations
99 worldwide.
100 </li>
101 <li>
102 <strong className="text-zinc-100">Google</strong> is contacted only when you initiate a Google sign-in and
103 receives the OAuth request parameters required to authenticate you.
104 </li>
105 <li>
106 <strong className="text-zinc-100">GitHub</strong> is contacted only when you initiate a GitHub sign-in and
107 receives the OAuth request parameters required to authenticate you.
108 </li>
109 </ul>
110 <p>We do not sell, rent, or share your personal information with anyone else.</p>
111 </Section>
112
113 <Section title="Where data is stored, and for how long">
114 <p>
115 Account, session, invite, and OAuth-client data is stored in Cloudflare D1. We retain account information for
116 as long as your account exists. Sessions are deleted when they expire or when you sign out. Audit records used
117 for abuse prevention may be retained briefly by Cloudflare's edge logs.
118 </p>
119 <p>
120 When you delete your account, your user row, linked identities, sessions, and stored OAuth tokens are removed.
121 Backups, if any, are rotated on a short cadence and overwritten in due course.
122 </p>
123 </Section>
124
125 <Section title="Your choices">
126 <p>From your account page you can:</p>
127 <ul className="list-disc space-y-2 pl-6">
128 <li>Update your display name and handle.</li>
129 <li>Change your password.</li>
130 <li>Link or unlink a Google or GitHub identity.</li>
131 <li>Sign out of any active session, including remote ones.</li>
132 <li>See which OAuth applications have been authorized to receive your identity, and revoke them.</li>
133 </ul>
134 <p>
135 To delete your tessera account or to request a copy of the personal information we hold about you, email{" "}
136 <ContactEmail address={operatorContactEmail} /> from the address on file. If you are a California resident,
137 you have the right to know, delete, correct, and limit the use of your personal information under the
138 California Consumer Privacy Act; we honor these rights regardless of where you reside.
139 </p>
140 <p>
141 You can also revoke tessera's access to your Google account at any time from your{" "}
142 <a
143 href="https://myaccount.google.com/permissions"
144 target="_blank"
145 rel="noopener noreferrer"
146 className="text-zinc-100 underline decoration-zinc-700 underline-offset-[6px] transition-colors hover:text-accent-400 hover:decoration-accent-500/60"
147 >
148 Google account permissions page
149 </a>
150 .
151 </p>
152 </Section>
153
154 <Section title="Cookies">
155 <p>
156 tessera sets a session cookie when you sign in and may set short-lived cookies during the OAuth flow to
157 protect against cross-site request forgery. We do not use cookies for advertising or analytics.
158 </p>
159 </Section>
160
161 <Section title="Security">
162 <p>
163 Passwords are stored as Argon2id hashes. OAuth access and refresh tokens are encrypted with a server secret
164 before storage. Identity tokens are signed with keys whose private halves are encrypted at rest and rotated on
165 a regular cadence. Connections to tessera are served over TLS.
166 </p>
167 <p>
168 No system is perfectly secure; if you discover a vulnerability, please email{" "}
169 <ContactEmail address={operatorContactEmail} />.
170 </p>
171 </Section>
172
173 <Section title="International transfers">
174 <p>
175 tessera is operated from the United States and uses providers that may process data in the United States and
176 other countries. By using tessera you consent to the transfer of your information to these locations.
177 </p>
178 </Section>
179
180 <Section title="Children">
181 <p>
182 tessera is not directed to children under 13. We do not knowingly collect personal information from children
183 under 13. If you believe a child has provided us with personal information, contact us and we will remove it.
184 </p>
185 </Section>
186
187 <Section title="Changes to this policy">
188 <p>
189 We may update this policy from time to time. The effective date at the top of this page indicates when it was
190 last revised. Material changes will be communicated through the service or by email to the address on file.
191 </p>
192 </Section>
193
194 <Section title="Contact">
195 <p>
196 Questions about this policy or about your information can be sent to{" "}
197 <ContactEmail address={operatorContactEmail} />.
198 </p>
199 </Section>
200 </div>
201 );
202};