ROwO Auth is a student verification portal that links WeChat IDs to verified student identities using multiple verification methods (ADFS, university email, Discord, GitHub, and manual review).
- A React + Vite frontend for:
- public verification lookup
- user verification flows
- admin/moderator account management
- A Cloudflare Worker backend (
backend/worker.js) for:- verification APIs
- admin APIs
- rename token flow
- email sending, Discord, and GitHub integrations
- Frontend: React, TypeScript, Vite, Tailwind CSS
- Backend: Cloudflare Worker (JavaScript), D1 database binding (
auth_database) - Integrations: AWS SES (email), Discord OAuth/API, GitHub OAuth/API
This repo is an npm workspaces monorepo:
apps/main/: the consumer-facing site (rowo.link) — verification flows, admin panel, user centerapps/developers/: the developer panel (developers.rowo.link) — OAuth client CRUD, API docs, playgroundpackages/shared/: code shared by both frontends (session/JWT utilities, common types)backend/worker.js: Cloudflare Worker — single API backend serving both apps atapi.rowo.linkbackend/migrations/: ordered D1 schema migrationspackage.json: workspace root with cross-workspace scripts
- Node.js 20+ (recommended)
- npm
- A deployed backend API endpoint (or local Worker endpoint) for frontend calls
- Install dependencies from the repo root (wires the workspaces together):
npm install
- Configure each app's API target in its own
package.json:apps/main/package.json→config.api_endpoint,config.icon_urlapps/developers/package.json→ same fields
- Start a dev server:
npm run dev:main(port 5173)npm run dev:developers(port 5174)
- Open the local Vite URL shown in terminal.
Each app reads constants from its own package.json (apps/main/package.json, apps/developers/package.json):
config.api_endpoint: base URL used by frontend API requestsconfig.icon_url: icon URL injected into HTML
These values are exposed as __API_ENDPOINT__ and __ICON_URL__ at build time. All OAuth client IDs, scopes, and redirect URIs live on the backend (see GET /api/oauth/redirect/:provider); the frontend never constructs OAuth authorize URLs.
.env.example includes:
APP_URL: public URL where the app is hosted
Backend backend/worker.js expects runtime bindings/secrets such as:
- Database binding:
auth_database - Security:
SENSITIVE_DATA_HASH_SECRET,ADFS_JWT_SECRET - Email (AWS SES):
AWS_ACCESS_KEY_ID,AWS_SECRET_ACCESS_KEY,AWS_REGION,SES_FROM_EMAIL,SES_FROM_NAME - Discord:
DISCORD_CLIENT_ID,DISCORD_CLIENT_SECRET,DISCORD_REDIRECT_URI,DISCORD_BOT_TOKEN - GitHub:
GITHUB_CLIENT_ID,GITHUB_CLIENT_SECRET - Policy/rate-limit options:
EMAIL_SENDS_PER_MINUTE,CORS_ALLOW_ORIGINS,ALLOWED_EMAIL_DOMAIN,ADFS_PROVIDER_ENDPOINT
npm run dev:main: start the main site dev server (port 5173)npm run dev:developers: start the developer panel dev server (port 5174)npm run build:main/npm run build:developers: build each frontend toapps/<name>/distnpm run preview:main/npm run preview:developers: preview a production build locallynpm run lint: TypeScript project-references build (tsc -b) across all workspacesnpm run dev:worker: run Worker locally with Wrangler confignpm run deploy:worker: deploy Worker usingbackend/wrangler.tomlnpm run deploy:worker:prod: deploy Worker toproductionenvironment
Each frontend deploys as its own Cloudflare Pages project, both connected to this repo:
| CF Pages project | Build command | Output directory | Custom domain |
|---|---|---|---|
rowo-main |
npm run build:main |
apps/main/dist |
rowo.link |
rowo-developers |
npm run build:developers |
apps/developers/dist |
developers.rowo.link |
Leave the Root directory setting blank — builds must run from the repo root so npm workspaces resolve. Cloudflare auto-spawns preview deployments for non-production branches.
This repository includes the Worker deployment config at backend/wrangler.toml for backend/worker.js. The D1 database_id values intentionally use ${D1_DATABASE_ID_*} placeholders so the real IDs do not need to be committed.
- Create or identify the
rowo-authWorker and the preview and production D1 databases. - In the Worker's Settings > Variables & Secrets, add the runtime secrets listed below. Runtime secrets must not be configured only as build variables because build variables are not available to the deployed Worker.
- Keep plaintext runtime variables in
backend/wrangler.tomlunder[vars]and[env.production.vars](already scaffolded in this repo). - Connect the Worker to this Git repository under Settings > Builds.
ADFS_JWT_SECRET: Authenticate requests and ensure they come from the ADFS provider.AWS_ACCESS_KEY_ID: Send verification emails through AWS SES.AWS_SECRET_ACCESS_KEY: Send verification emails through AWS SES.DISCORD_BOT_TOKEN: Configure the Discord bot for Discord-based verification workflow.DISCORD_CLIENT_SECRET: Configure Discord OAuth for Discord-based verification workflow.GITHUB_CLIENT_SECRET: Configure GitHub OAuth for GitHub-based verification workflow.SENSITIVE_DATA_HASH_SECRET: Hash identifiable student information for privacy compliance.
ALLOWED_EMAIL_DOMAIN = "uwaterloo.ca": Allow email domain for email-based verification.ADFS_PROVIDER_ENDPOINT = "https://adfs.example.edu/login": External ADFS provider endpoint the unified/api/oauth/redirect/adfsroute 302s to.AWS_REGION = "us-east-1": AWS SES region for verification email sending.CORS_ALLOW_ORIGINS = "*": Configure CORS allowlist.DISCORD_CLIENT_ID = "...": Public Discord OAuth client ID used to build the authorize URL.DISCORD_REDIRECT_URI = "https://rowo.link/verify/discord/callback": Callback URL for Discord-based verification workflow.EMAIL_SENDS_PER_MINUTE = "60": Email send rate limit for email-based verification workflow.GITHUB_CLIENT_ID = "": Public GitHub OAuth client ID used to build the authorize URL.GITHUB_REDIRECT_URI = "https://rowo.link/verify/github/callback": Callback URL for GitHub-based verification workflow.SES_FROM_EMAIL = "verification@rowo.link": Sender address for verification emails.SES_FROM_NAME = "ROwO Auth": Sender display name for verification emails.
Trusted Discord guild/role pairs are configured in D1 (discord_trusted_servers table), not in environment variables.
Configure the production build under Worker > Settings > Builds > Build Configuration:
| Setting | Value |
|---|---|
| Production branch | main |
| Root directory | Leave blank so commands run from the repository root |
| Build command | Use the render command below |
| Deploy command | npx wrangler deploy --config backend/wrangler.deploy.toml --env production |
| Non-production branch builds | Disable unless a separate preview deployment workflow is configured |
Under Settings > Builds > Build Variables and Secrets, add:
D1_DATABASE_ID_PREVIEW: preview D1 database UUID.D1_DATABASE_ID_PRODUCTION: production D1 database UUID.CLOUDFLARE_ACCOUNT_ID: Cloudflare account ID.
The D1 IDs may be stored as build secrets. They are used only while rendering and deploying the Wrangler configuration and are not Worker runtime variables.
Wrangler does not expand arbitrary ${VAR} placeholders inside wrangler.toml. Set the following as the Cloudflare Build command to render an ephemeral configuration using the Node.js runtime provided by Workers Builds:
node -e 'const fs=require("node:fs");const names=["D1_DATABASE_ID_PREVIEW","D1_DATABASE_ID_PRODUCTION"];let s=fs.readFileSync("backend/wrangler.toml","utf8");for(const n of names){const v=process.env[n];if(!v)throw new Error("Missing build variable: "+n);s=s.replaceAll("${"+n+"}",v)}fs.writeFileSync("backend/wrangler.deploy.toml",s)'The generated backend/wrangler.deploy.toml remains in Cloudflare's temporary build workspace and is consumed by the Deploy command. It is already excluded by .gitignore and is never committed.
If envsubst is known to be available in the selected build image, the equivalent Build command is:
envsubst '$D1_DATABASE_ID_PREVIEW $D1_DATABASE_ID_PRODUCTION' < backend/wrangler.toml > backend/wrangler.deploy.tomlThe Node.js command is preferred because Workers Builds guarantees Node.js support, while envsubst is not guaranteed to be installed.
Apply backend/schema.sql to initialize or migrate the auth_database D1 database:
- Local/default database:
npx wrangler d1 execute rowo-auth-db --file backend/schema.sql --config backend/wrangler.toml
- Production environment database:
npx wrangler d1 execute rowo-auth-db --file backend/schema.sql --config backend/wrangler.toml --env production
If your D1 database name differs from rowo-auth-db, replace it in the command accordingly.
The schema includes discord_trusted_servers and seeds one active trusted guild/role pair. Update that table in D1 to manage trusted Discord sources.
For an existing deployment upgrading to admin notification preferences, run the following ALTER statements (skip if backend/schema.sql was applied fresh):
npx wrangler d1 execute rowo-auth-db --command "ALTER TABLE admins ADD COLUMN notification_email TEXT;" --config backend/wrangler.toml --env production
npx wrangler d1 execute rowo-auth-db --command "ALTER TABLE admins ADD COLUMN manual_notification_enabled INTEGER NOT NULL DEFAULT 0;" --config backend/wrangler.toml --env productionCore verification routes:
GET /api/verify/:wechatIdPOST /api/adfs/create-codePOST /api/verify/adfsPOST /api/verify/emailPOST /api/verify/discord/callbackPOST /api/verify/discord/connectPOST /api/verify/github/callbackPOST /api/verify/github/connectPOST /api/verify/manual
ROwO Account routes (username + password):
POST /api/user/registerPOST /api/user/loginPOST /api/user/logoutGET /api/user/mePOST /api/user/bind-wechatPOST /api/user/change-wechatPOST /api/user/change-password
Admin routes:
POST /api/admin/loginPOST /api/admin/rotate-tokenGET /api/admin/preferencesPOST /api/admin/preferencesGET /api/admin/accountsGET /api/admin/statsGET /api/admin/blacklistPOST /api/admin/batch/verifyPOST /api/admin/batch/blacklistGET|POST /api/admin/accounts/:wechatId/infoPUT|DELETE /api/admin/info/:idPOST /api/admin/accounts/:wechatId/(revoke|unrevoke|manual|blacklist|unblacklist)GET /api/admin/usersPOST /api/admin/users/:id/reset-passwordPOST /api/admin/users/:id/unbind-wechat
Developer panel routes (any logged-in user; scoped to their own clients):
GET /api/developers/oauth-clientsPOST /api/developers/oauth-clientsGET /api/developers/oauth-clients/:client_idPATCH /api/developers/oauth-clients/:client_idPOST /api/developers/oauth-clients/:client_id/rotate-secretDELETE /api/developers/oauth-clients/:client_id
- Never commit real secrets or tokens.
- Keep admin tokens private and rotate them when exposed.
- Restrict
CORS_ALLOW_ORIGINSin production (avoid*where possible). - Configure
ALLOWED_EMAIL_DOMAINto enforce institutional email domain policy. - Internal backend exceptions are logged server-side and returned to clients as generic error messages.
- Core account identity correlation fields are stored using SHA-256 based keyed hashing.
- Some operational values are plaintext by design (for example WeChat IDs and moderation/admin metadata).
- Pending email verification data includes plaintext normalized email until verification completes or expires.
- Subprocessors/services used by this project:
- Cloudflare Workers + D1 (runtime and database)
- AWS SES (verification email delivery)
- Discord APIs (OAuth and guild/role verification)
- GitHub APIs (OAuth and verified-email domain check)
- Retention behavior:
- verification artifacts and rename tokens are short-lived
- rate-limit rows are pruned periodically
- account/moderation records persist until updated or manually removed
This project is licensed under the MIT License. See LICENSE for details.
- Create a feature branch.
- Make focused changes with clear commit messages.
- Run:
npm run lint- any local verification flows you changed
- Open a pull request with:
- change summary
- test/verification notes
- any config migration notes
- Frontend cannot reach backend:
- verify
config.api_endpointin the relevant app'spackage.json(apps/main/package.jsonorapps/developers/package.json) - ensure backend
CORS_ALLOW_ORIGINSincludes your frontend origin
- verify
- Email verification fails:
- check AWS SES credentials, sender identity, and region
- Discord verification fails:
- check OAuth redirect URI and guild/role related env vars
- GitHub verification fails:
- check
GITHUB_CLIENT_ID/GITHUB_CLIENT_SECRETandGITHUB_REDIRECT_URIinbackend/wrangler.toml - ensure the GitHub OAuth App's "Authorization callback URL" matches
GITHUB_REDIRECT_URI - user's GitHub account must have at least one verified email under
ALLOWED_EMAIL_DOMAIN
- check
- Admin panel cannot authenticate:
- ensure admin token exists in backend data and is sent as Bearer token