Administrative portal for NYJC Campus, providing staff access to campus management features through a secure OAuth-based authentication system.
The Campus Admin Portal is a Flask web application that integrates with the Campus authentication service to provide authenticated access to administrative features. It uses the Campus OAuth2 flow with Google Workspace sign-in for secure authentication.
- OAuth2 authentication via Campus auth service
- Google Workspace integration (nyjc.edu.sg domain)
- Session management with Flask sessions
- Secure login flow with redirect support
- Dashboard for authenticated users
- Framework: Flask 3.0+
- Authentication: Campus OAuth2 + Google Workspace
- Python: 3.11, 3.12, 3.13
- Package Management: Poetry
- Dependencies:
campus-api-python- Campus API client librarycampus-suite- Campus backend servicesflask- Web frameworkpython-dotenv- Environment configuration
- Python 3.11, 3.12, or 3.13
- Poetry
- Access to Campus development environment
- OAuth client credentials (CLIENT_ID and CLIENT_SECRET)
-
Clone the repository (if not already done):
git clone https://github.com/nyjc-computing/campus-admin.git cd campus-admin -
Install dependencies:
poetry install
-
Configure environment variables:
Copy the example file and fill in the values (see the Environment Variables table below for details):
cp .env.example .env
At minimum, set
SECRET_KEY,ADMINS,CLIENT_ID,CLIENT_SECRET, andPUBLIC_URL.
Start the Flask development server:
poetry run flask --app apps.admin runThe application will be available at http://localhost:5000. Sign-in
goes through the Campus auth service (Google Workspace), so the OAuth
client in CLIENT_ID/CLIENT_SECRET must have
http://localhost:5000/finalize_login registered as a redirect URI
(register it through this portal's own Clients page, or with campus-cli).
campus-admin/
├── apps/
│ └── admin/ # Main application package
│ ├── __init__.py # App factory, all routes, templates/static
│ └── templates/ # Jinja templates
├── scripts/ # Utility and test scripts
│ ├── test_auth.py # Authentication diagnostics
│ ├── test_oauth_flow.py # End-to-end OAuth testing
│ └── refresh-dependencies.sh # Dependency refresh utility
├── docs/ # Documentation
│ ├── browser-automation.md # Playwright setup guide
│ └── TESTING.md # Testing guide
├── .env.example # Environment template (copy to .env)
├── .github/workflows/ci.yml # Ruff + smoke tests (Python 3.11–3.13)
├── pyproject.toml # Poetry dependencies
└── README.md # This file
- User visits
/login?next=/dashboard - Application redirects to Campus auth service with OAuth parameters
- Campus auth redirects to Google Workspace sign-in
- User authenticates with Google (nyjc.edu.sg account)
- Google redirects back to Campus auth with authorization code
- Campus auth creates session and redirects to
{PUBLIC_URL}/finalize_login - Application validates session and redirects to requested page (
/dashboard)
Copy .env.example to .env and fill in the values. The app reads
SECRET_KEY, ADMINS, and PUBLIC_URL directly; CLIENT_ID,
CLIENT_SECRET, ENV/CAMPUS_ENV, and CAMPUS_AUTH_URL/CAMPUS_API_URL
are read inside campus-python.
| Variable | Required | Description |
|---|---|---|
SECRET_KEY |
Yes | Flask session signing key. The app refuses to start without it. |
ADMINS |
Yes | Semicolon-separated campus user ids allowed to use the admin routes (/clients, /users). A campus user id is the user's full email address (e.g. jane_doe@nyjc.edu.sg); signed-in users not listed get 403. |
CLIENT_ID |
Yes | OAuth client ID of this portal, from the Campus auth service (read by campus-python in "server" mode). |
CLIENT_SECRET |
Yes | OAuth client secret for CLIENT_ID. |
PUBLIC_URL |
Recommended | Public origin of this portal. The OAuth callback is {PUBLIC_URL}/finalize_login, and that exact URI must be registered as a redirect URI on the client. Falls back to https://{HOSTNAME} (deprecated). |
ENV |
No | Campus environment tier: development (default), staging, or production. Determines the auth/API service URLs campus-python uses. CAMPUS_ENV is accepted as an alias. |
CAMPUS_AUTH_URL |
No | Explicit Campus auth service base URL; takes precedence over the ENV tier. Set this for non-standard deployments. |
CAMPUS_API_URL |
No | Explicit Campus API service base URL; same precedence rules. |
HOSTNAME, DEPLOY |
— | Deprecated, ignored for URL resolution since campus-api-python#53 (a DeprecationWarning is emitted if they would previously have been used). |
Service URL resolution order (campus-python): explicit CAMPUS_AUTH_URL /
CAMPUS_API_URL, then the ENV tier — development → Railway dev
deployments, staging → {service}.campus.nyjc.dev, production →
{service}.campus.nyjc.app.
Decision (2026-10-01, issue #8): deploy as a Railway service, matching the rest of the Campus ecosystem. Local runs remain the supported development workflow.
- Why Railway: the portal is the interface for registering OAuth
redirect URIs (issue #7), which every Railway-hosted Campus consumer
needs before campus#651 strict enforcement flips — it must be
reachable without any particular developer's laptop running. The
campus-admin.up.railway.appproject already exists but runs pre-refresh code; redeploying the currentmainalso resolves #5. - Security: every admin route is gated by Campus OAuth sign-in plus
the
ADMINSallowlist, so a deployed instance exposes admin functionality only to listed emails. - Deploying: set the same environment variables from
.env.examplein the Railway service (PUBLIC_URL=https://campus-admin.up.railway.app, and registerhttps://campus-admin.up.railway.app/finalize_loginas a redirect URI on the portal's client). Performing the redeploy needs Railway access and is tracked with #5.
For local development, the workflow is: cp .env.example .env, fill it
in, poetry run flask --app apps.admin run.
If you make changes to the local campus-api-python or campus-suite packages:
./scripts/refresh-dependencies.shThis will update poetry.lock and reinstall dependencies from the latest commits.
See docs/TESTING.md for comprehensive testing documentation.
Quick test of OAuth flow:
poetry run python scripts/test_oauth_flow.pyFor end-to-end testing with Playwright, see docs/browser-automation.md.
Ensure your .env file contains a SECRET_KEY value. Generate one with:
python -c "import secrets; print(secrets.token_hex(32))"- Verify
CLIENT_IDandCLIENT_SECRETare correct in.env - Ensure the OAuth client is configured in Campus auth service
- Check that redirect URIs include
{PUBLIC_URL}/finalize_login— for local development that ishttp://localhost:5000/finalize_login
The signed-in user is not listed in ADMINS. Entries are campus user
ids, which are full email addresses (e.g.
jane_doe@nyjc.edu.sg), separated by semicolons. Remember to restart
flask run after changing .env.
Run the diagnostic script:
poetry run python scripts/test_auth.pyThis will test:
- Environment configuration
- Campus client initialization
- Auth service connectivity
- Flask app setup
- Route configuration
- Follow the existing code style (enforced by Ruff)
- Test changes with
scripts/test_oauth_flow.py - Update documentation as needed
- Commit with descriptive messages
- campus - Campus backend services
- campus-api-python - Python API client
Internal use by NYJC Computing team.