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 Worker deployment config at backend/wrangler.toml for backend/worker.js.
- Update placeholders in
backend/wrangler.toml:account_iddatabase_idvalues underd1_databases
- Authenticate Wrangler locally:
npx wrangler login
- Add required Worker secrets (encrypted values):
npx wrangler secret put SENSITIVE_DATA_HASH_SECRET --config backend/wrangler.toml --env productionnpx wrangler secret put ADFS_JWT_SECRET --config backend/wrangler.toml --env productionnpx wrangler secret put AWS_ACCESS_KEY_ID --config backend/wrangler.toml --env productionnpx wrangler secret put AWS_SECRET_ACCESS_KEY --config backend/wrangler.toml --env productionnpx wrangler secret put DISCORD_CLIENT_SECRET --config backend/wrangler.toml --env productionnpx wrangler secret put DISCORD_BOT_TOKEN --config backend/wrangler.toml --env productionnpx wrangler secret put GITHUB_CLIENT_SECRET --config backend/wrangler.toml --env production
- Set plaintext variables in
backend/wrangler.tomlunder[vars]and[env.production.vars](already scaffolded in this repo).
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.
- Default deploy:
npm run deploy:worker
- Production deploy (for
mainbranch release pipeline):npm run deploy:worker:prod
For Cloudflare CI/CD, use the same production command (npx wrangler deploy --config backend/wrangler.toml --env production) in your main branch pipeline.
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