Draft
Privacy
Last updated
Draft. This page is not in effect. The owner has not approved it. Items marked [TO CONFIRM] are not settled by the product code.
Accounts and sign-in
You sign in with an email address. There is no password. You enter the address, and the server asks Firebase Authentication to create a one-time sign-in link. The server also makes a 6-digit code. It stores the email, a hash of the code, the link, an expiry 15 minutes out, and a count of wrong tries. That record is deleted when the code works, when it expires, or after too many wrong tries. The browser app cannot read it.
Resend delivers the message. The only From address in this repository is Taverning <noreply@mail.taverning.com>. [TO CONFIRM] whether the host overrides it with the EMAIL_FROM setting.
Opening the link signs you into Firebase Authentication for the Firebase project tpk-group-8cdc8. The first time, the app writes a user record with your email, an empty display name, the time zone reported by your browser, and an empty list of campaigns hidden from your calendar. It also creates a random calendar token. You set the display name yourself (40 characters at most). That name is what the table sees.
If a campaign already has a seat or a pending membership with the same email, the first sign-in attaches it to this account.
The iOS app is a shell that loads this website. Sign-in there is the same email link. The app code does not include Sign in with Apple, and the iOS notes still list it as unfinished, so the app does not receive an Apple ID.
You must be 13 or older to have an account.
Taverning is operated by Seth Thomas. Privacy requests go to privacy@taverning.com.
Campaigns, sessions, polls, and players
A campaign stores its name, system, place, time zone, usual night, cover image, character sheets (name, class, subclass, level, portrait), a short "previously on," a ledger, and the player list. Each player row has an email, a display name, an optional character name, a role (organizer, co-organizer, or member), optional labels such as "dm," and whether that person turned off reminder email.
A session stores the start and end, title, place, notes, who is in or out (and an optional note), and a recap as text or a PDF. An organizer can upload the PDF. A separate import, which requires a server secret, can also write the recap, the PDF, the "previously on" line, and a character summary. Those import routes do not call an AI API.
A poll stores the dates, each member's vote, and votes from a public link. A public vote stores the name the person typed and a hash of a token that stays in that browser. The raw token is not stored on the server.
Before someone has an account, an organizer can add a roster seat: a name, an optional character name, an optional email, and an optional phone number, plus a secret link that claims the seat. Email, phone, and that link are returned to the organizer and co-organizer. Other members of the campaign get the name, the character name, and whether the seat has been claimed.
The data model has unused fields reserved for a Discord bot. The app does not send campaign data to Discord.
Who can see campaign data
Campaign data is visible only to that campaign's members, except for the cases below.
The database rules let a signed-in member read that campaign, its sessions, its polls, its character summaries, and its member list. A signed-out visitor cannot read those documents. A signed-in person who is not a member cannot read them either. The campaign document can also be read by the account stored as its owner. Creating a campaign writes that account in as the organizer, so the owner is a member in ordinary use.
The player roster is closed to the browser database entirely. The server returns it only after it checks membership, as described above.
You can read only your own user record. The rules do not let you delete it.
These links are served by the server with a privileged database key, so the member rule does not apply to them:
- A poll link shows the poll title, the campaign name, the dates, vote counts, the names and character names on the player list, who has not voted, and the names and picks on guest ballots. It does not show email addresses or phone numbers.
- An invite link shows the campaign name, cover, place, who sent the invite, and the role.
- A seat link shows the campaign name and the name and character on that seat.
- A calendar link is a secret URL. Anyone with it can read the session times, titles, and places for the campaigns on that account.
Campaign recaps and stats are visible only to that campaign's members.
Campaign covers can be read by members of that campaign. Portrait files stored under recaps/avatars can be read by any signed-in user, not only members of one campaign.
Any signed-in user can read the list of emails allowed to create campaigns. That list is not campaign notes. Only those addresses, or an address with a creator record, can create a campaign.
Email, push, and the iOS app
Sign-in email is described above. Resend receives the recipient address, the link, and the 6-digit code in order to send that message.
A route can email members three days before a session and again three hours before. It skips anyone marked out, and anyone who turned reminders off for that campaign. The message includes the campaign name, the session time and place, the note, who is out, and the "previously on" line. It sends only when the server has a Resend key and a reminder From address, and only when something calls the route with the cron secret. This repository does not schedule that call, and it does not name the reminder From address. [TO CONFIRM] whether the host schedules that route or sets a From address outside the repository.
If you allow notifications in the iOS app, the app sends an Apple push device token to the server. The server stores it on your user record with the platform set to iOS. Turning notifications off deletes that stored token. This repository does not send those pushes. The iOS notes list a sender as still to be built.
The iOS app also opens this site, plays a short haptic tap, and follows links for taverning.com inside the app. Subscribing to the calendar link hands that URL to the calendar app you choose. A place link opens Apple Maps or Google Maps on your device. The server does not send a separate copy of the campaign to Apple for those links.
What stays on your device
Firebase Authentication keeps the sign-in session in IndexedDB, or in local storage when the site is installed to a home screen. The app also stores, in this browser only:
- the email you typed, so the sign-in link can be finished
- a display-name draft, the theme, and a larger-text choice
- the public-poll token and the name you voted under
- which recaps this browser has already opened
Session storage holds in-progress join details and which tab you came from. It is cleared when the tab closes.
The older single-file page sets a cookie named __tpk_token to the Firebase sign-in token, for one hour, so /maloren and /iven can check who is signed in. The Next.js app does not set that cookie. It only reads it on those two paths.
Invite requests and rate limits
The home page can store an invite request: your email, an optional line about the table (140 characters), the word "landing," the deploy environment, the time, and how many times that email was submitted. A hidden field is ignored. The browser cannot read this list.
Sign-in, invite requests, roster edits, and public votes are rate limited. The limit uses the IP address from the request, and sometimes the email, the poll, or the guest token. The server stores a hash of that key, a count, and an expiry time. It does not store the raw IP in the document. Nothing in this repository deletes those rows. [TO CONFIRM] whether the Firebase console is set to delete them when the expiry time passes.
Who processes this
- Google Firebase, project tpk-group-8cdc8, provides Authentication, Cloud Firestore, and Cloud Storage. The server uses the Firebase Admin SDK to read and write the same project.
- Vercel hosts the site and runs the server routes. A request includes the IP address, the URL, and the usual browser headers Vercel needs to answer it.
- Resend sends the sign-in email and, when the reminder route runs, the session email.
- Apple provides the iOS shell and, if you allow notifications, the push token. The app does not use Sign in with Apple.
No other processor is named in this repository.
Analytics
The app does not start a product-analytics library. It does not include Vercel Analytics, Google Analytics, or a call that starts Firebase Analytics. The Firebase package ships an analytics module that this app does not import.
[TO CONFIRM] whether Vercel, Google, or Firebase keep request logs or console analytics for the project, and for how long. Nothing in this repository sets that.
How long data is kept, and deletion
Account and campaign data is kept until that account or that campaign is deleted. Sign-in codes are the exception: they are deleted within 15 minutes, as described above.
On the account screen, Delete my account asks you to type DELETE. The server checks the signed-in user and ignores any other id. It then deletes that user's record, devices, calendar token, memberships, roster seats, votes, attendance marks, and a portrait whose character sheet names their email. It then deletes the Firebase Authentication user.
If you organize a campaign that still has other players, deletion stops and nothing is removed. Transfer that campaign first. If you are the only player, that campaign is deleted with the account, including its sessions, polls, invites, and uploaded files. A player who is not the organizer can also leave one campaign without deleting the account. The organizer can remove a player, archive a campaign, or delete that campaign from its page.
You must be 13 or older. The site does not ask for a date of birth.
These terms are governed by Oregon law, and disputes are heard in the courts of Oregon. See the terms.