Skip to content

Commit db5b2be

Browse files
Merge pull request #112 from dipamsen/user-auth
Add single user auth, improve documentation
2 parents d601757 + 19110af commit db5b2be

File tree

5 files changed

+93
-15
lines changed

5 files changed

+93
-15
lines changed

README.md

Lines changed: 69 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
- [About The Project](#about-the-project)
3737
- [Development](#development)
3838
- [Deployment](#deployment)
39+
- [Backend](#backend)
3940
- [Environment Variables](#environment-variables)
4041
- [Contact](#contact)
4142
- [Maintainer(s)](#maintainers)
@@ -45,6 +46,7 @@
4546
</details>
4647

4748
## About The Project
49+
4850
<div align="center">
4951
<img width="60%" alt="image" src="./frontend/public/banner.png">
5052
</div>
@@ -56,21 +58,75 @@ IQPS was originally created by [Shubham Mishra](https://github.com/grapheo12) in
5658
> Currently in active development. Get involved at our [Slack](https://slack.metakgp.org/).
5759
5860
## Development
61+
5962
1. Clone this repository.
6063
2. For starting the backend:
61-
- Change directory to backend `cd backend`
62-
- Make the env file by copying the template: `cp .env.template .env`
63-
- Fill the env variable and set `DB_HOST=localhost` for running locally for development
64-
- Start the DB by running `docker compose -f docker-compose.dev.yaml up -d`
65-
- Start the Rust backend by running `cargo run .`
64+
- Change directory to backend `cd backend`
65+
- Make the env file by copying the template: `cp .env.template .env`
66+
- Fill the env variables and set `DB_HOST=localhost` for running locally for development (see [Environment Variables](#environment-variables)). Make sure to create corresponding folders for `STATIC_FILE_STORAGE_LOCATION`, `UPLOADED_QPS_PATH`, and `LIBRARY_QPS_PATH`.
67+
- Set up the database (see [Database](#database))
68+
- Start the Rust backend by running `cargo run .`
6669
3. Set up the frontend by running `pnpm install` and then `pnpm start` in the `frontend/` directory.
6770
4. Profit.
6871

72+
### Database
73+
74+
To initialise the database for the first time:
75+
76+
1. Set environment variables for Postgres in the `.env` file.
77+
2. Start the database by running `docker compose -f docker-compose.dev.yaml up -d`.
78+
3. Initialise the database:
79+
- Open a shell in the docker container by running `docker compose -f docker-compose.dev.yaml exec database-dev bash`.
80+
- Connect to the database by running `psql -U $POSTGRES_USER -d $POSTGRES_DB`.
81+
- Run the queries in `INIT_DB` in [`backend/src/db/queries.rs`](./backend/src/db/queries.rs) to initialise the database.
82+
83+
To run the pre-initialised database:
84+
85+
1. Start the database by running `docker compose -f docker-compose.dev.yaml up -d`.
86+
87+
For Production:
88+
89+
1. Set environment variables for Postgres in the `.env` file.
90+
2. Start the database by running `docker compose -f docker-compose.yaml up -d`.
91+
3. Initialise the database:
92+
- Open a shell in the docker container by running `docker compose -f docker-compose.yaml exec iqps-backend bash`.
93+
- Connect to the database by running `psql -U $POSTGRES_USER -d $POSTGRES_DB`.
94+
- Run the queries in `INIT_DB` in [`backend/src/db/queries.rs`](./backend/src/db/queries.rs) to initialise the database.
95+
96+
### Authentication
97+
98+
IQPS uses GitHub OAuth for authentication to the `/admin` page. To set up authentication:
99+
100+
1. Create a new OAuth app on GitHub.
101+
- Go to https://github.com/settings/developers and create a new OAuth app.
102+
- Set the Homepage URL to `http://localhost:5173` and Authorization callback URL to `http://localhost:5173/oauth`.
103+
- Once created, generate a client secret. Copy the client ID and secret into the `.env` file.
104+
2. Set the Authentication environment variables in the `.env` file.
105+
106+
For Production:
107+
108+
1. Create a new OAuth app on GitHub. (Should be from the same GitHub account as the organization)
109+
- Go to https://github.com/settings/developers and create a new OAuth app.
110+
- Set the Homepage URL to `<prod-url>` and Authorization callback URL to `<prod-url>/oauth`.
111+
- Once created, generate a client secret. Add the client ID and secret to environment variables.
112+
2. Set the Authentication environment variables.
113+
114+
#### OAuth Flow
115+
116+
- Github OAuth documentation: https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/authorizing-oauth-apps
117+
118+
On visiting `/admin`, if the user is not logged in, they get redirected to the GitHub OAuth page. After the user logs in, GitHub redirects them back to our `/oauth` endpoint with a code. The backend then uses this code to fetch an access token and username. The username is then checked against the allowed admins. If so, a JWT token is generated with the user's username and sent back to the frontend. The frontend then stores this token in local storage and sends it with every request to the backend. The backend verifies this token and allows access to admin functions.
119+
120+
A user is considered as an admin if they are a part of the team `GH_ORG_TEAM_SLUG` in `GH_ORG_NAME`, or if their username is in the `GH_ADMIN_USERNAMES` list.
121+
69122
### Crawler
123+
70124
[WIP: Steps to locally set up crawler]
71125

72126
## Deployment
127+
73128
### Backend
129+
74130
0. Set up [MetaPloy](https://github.com/metakgp/metaploy) **for production**.
75131
1. Clone this repository at a convenient location such as `/deployments`.
76132
2. `cd backend/`
@@ -79,24 +135,31 @@ IQPS was originally created by [Shubham Mishra](https://github.com/grapheo12) in
79135
5. Optionally set up a Systemd service to start the wiki on startup or use this [deployment github workflow](./.github/workflows/deploy.yaml).
80136

81137
### Environment Variables
138+
82139
Environment variables can be set using a `.env` file. Use the `.env.template` files for reference. See `backend/src/env.rs` for more documentation and types.
83140

84141
#### Backend
142+
85143
##### Database (Postgres)
144+
86145
- `DB_NAME`: Database name
87146
- `DB_HOST`: Database hostname (eg: `localhost`)
88147
- `DB_PORT`: Database port
89148
- `DB_USER`: Database username
90149
- `DB_PASSWORD`: Database password
91150

92151
##### Authentication
152+
93153
- `GH_CLIENT_ID`: Client ID of the Github OAuth app.
94154
- `GH_CLIENT_SECRET`: Client secret of the Github OAuth app.
95155
- `GH_ORG_NAME`: The name of the Github organization of the admins.
96156
- `GH_ORG_TEAM_SLUG`: The URL slug of the Github org team of the admins.
157+
- `GH_ORG_ADMIN_TOKEN`: Github token of organization admin (with `read:org` scope).
158+
- `GH_ADMIN_USERNAMES`: Comma separated list of Github usernames of the admins. (other than the org team members)
97159
- `JWT_SECRET`: A secret key/password for JWT signing. It should be a long, random, unguessable string.
98160

99161
##### Configuration
162+
100163
- `MAX_UPLOAD_LIMIT`: Maximum number of files that can be uploaded at once.
101164
- `LOG_LOCATION`: The path to a local logfile.
102165
- `STATIC_FILES_URL`: The URL of the static files server. (eg: `https://static.metakgp.org`)
@@ -107,11 +170,11 @@ Environment variables can be set using a `.env` file. Use the `.env.template` fi
107170
- `CORS_ALLOWED_ORIGINS`: A comma (,) separated list of origins to be allowed in CORS.
108171

109172
#### Frontend
173+
110174
- `VITE_BACKEND_URL`: The IQPS backend URL. Use `http://localhost:8080` in development.
111175
- `VITE_MAX_UPLOAD_LIMIT` The maximum number of files that can be uploaded at once. (Note: This is only a client-side limit)
112176
- `VITE_GH_OAUTH_CLIENT_ID` The Client ID of the Github OAuth app.
113177

114-
115178
## Contact
116179

117180
<p>

backend/.env.template

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ GH_CLIENT_SECRET=
99
GH_ORG_NAME=
1010
GH_ORG_TEAM_SLUG=
1111
GH_ORG_ADMIN_TOKEN=
12+
GH_ADMIN_USERNAMES=
1213

1314
JWT_SECRET=
1415

backend/src/auth.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ struct GithubMembershipResponse {
9696
/// Takes a Github OAuth code and creates a JWT authentication token for the user
9797
/// 1. Uses the OAuth code to get an access token.
9898
/// 2. Uses the access token to get the user's username.
99-
/// 3. Uses the username and and a admin's access token to verify whether the user is a member of the admins github team.
99+
/// 3. Uses the username and an admin's access token to verify whether the user is a member of the admins github team, or the admin themselves.
100100
///
101101
/// Returns the JWT if the user is authenticated, `None` otherwise.
102102
pub async fn authenticate_user(
@@ -152,6 +152,15 @@ pub async fn authenticate_user(
152152
.context("Error parsing username API response.")?
153153
.login;
154154

155+
// Check if the user is in the admin list
156+
if env_vars
157+
.gh_admin_usernames
158+
.split(",")
159+
.any(|x| x == username)
160+
{
161+
return Ok(Some(generate_token(&username, env_vars).await?));
162+
}
163+
155164
// Check the user's membership in the team
156165
println!(
157166
"https://api.github.com/orgs/{}/teams/{}/memberships/{}",

backend/src/db/queries.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ CREATE TABLE IF NOT EXISTS iqps (
1919
from_library BOOLEAN DEFAULT FALSE,
2020
upload_timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
2121
approve_status BOOLEAN DEFAULT FALSE,
22+
approved_by TEXT DEFAULT '',
23+
is_deleted BOOLEAN DEFAULT FALSE,
2224
fts_course_details tsvector GENERATED ALWAYS AS (to_tsvector('english', course_code || ' ' || course_name)) stored
2325
);
2426
CREATE INDEX IF NOT EXISTS iqps_fts ON iqps USING gin (fts_course_details);

backend/src/env.rs

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -34,20 +34,23 @@ pub struct EnvVars {
3434
/// OAuth app client id (public token)
3535
pub gh_client_id: String,
3636
#[arg(env)]
37+
/// An org admin's Github token (with the `read:org` permission)
38+
pub gh_org_admin_token: String,
39+
#[arg(env)]
40+
/// JWT encryption secret (make it a long, randomized string)
41+
jwt_secret: String,
42+
#[arg(env)]
3743
/// OAuth app client secret
3844
pub gh_client_secret: String,
39-
#[arg(env)]
45+
#[arg(env, default_value = "")]
4046
/// Github organization name
4147
pub gh_org_name: String,
42-
#[arg(env)]
48+
#[arg(env, default_value = "")]
4349
/// Github organization team slug (this team has access to admin dashboard)
4450
pub gh_org_team_slug: String,
45-
#[arg(env)]
46-
/// An org admin's Github token (with the `read:org` permission)
47-
pub gh_org_admin_token: String,
48-
#[arg(env)]
49-
/// JWT encryption secret (make it a long, randomized string)
50-
jwt_secret: String,
51+
#[arg(env, default_value = "")]
52+
/// The usernames of the admins (additional to org team members, comma separated)
53+
pub gh_admin_usernames: String,
5154

5255
// Other configs
5356
#[arg(env, default_value = "10")]

0 commit comments

Comments
 (0)