Skip to content
Open
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
c7f5edd
feat: integrate Keycloak IAM with unified first-admin bootstrap and D…
Jul 11, 2026
466e2d1
anchoring the static files to its project folder.
Dyutiman-shiv Jul 4, 2026
9129b70
removed the unnecessary creation of static folder.
Dyutiman-shiv Jul 4, 2026
2cbce9e
add some unique params
oMakaron Jul 4, 2026
646e360
change default logo name to the chapter name (help enforce uniqueness)
oMakaron Jul 5, 2026
f248e43
make migration
oMakaron Jul 5, 2026
70941a9
fixed a bug where logo does not take save the chapter name as default…
oMakaron Jul 5, 2026
ad5e10f
remade validation for unique non default
oMakaron Jul 9, 2026
5f2bed4
make migration
oMakaron Jul 9, 2026
69a1a88
replace unique logo logic
oMakaron Jul 11, 2026
749ca9e
override save to make none and empty path to default path
oMakaron Jul 11, 2026
bd8db26
make migration
oMakaron Jul 11, 2026
dcc934b
added API endpoint for self-service user signup/signin and usage docu…
Jul 11, 2026
37a6214
Merge branch 'main' into issue-55-User_Identity_and_Access_Management…
zephris Jul 11, 2026
60ef6e9
fix: add db_table to Chapter.Meta to match existing DB table name
Copilot Jul 11, 2026
4433ff0
linting and database migration fix
Jul 11, 2026
d8f0716
more linting
Jul 11, 2026
f0095fc
Merge branch 'main' into issue-55-User_Identity_and_Access_Management…
zephris Jul 14, 2026
e1a8750
Potential fix for pull request finding
zephris Jul 14, 2026
e934f02
Revert "Merge branch 'main' into issue-55-User_Identity_and_Access_Ma…
zephris Jul 14, 2026
f031de7
Merge branch 'main' into issue-55-User_Identity_and_Access_Management…
Jul 15, 2026
560cf15
fix conflictinging migrations
Jul 15, 2026
e08fad1
fixing github sync issues
Jul 15, 2026
191c3c6
further fixing backend mismatch from sync issues
Jul 15, 2026
1bd70ed
Merge branch 'main' into issue-55-User_Identity_and_Access_Management…
zephris Jul 15, 2026
82c2945
further fixing backend merge mismatch and grammar
Jul 15, 2026
90c68f8
Merge branch 'main' into issue-55-User_Identity_and_Access_Management…
zephris Jul 18, 2026
379de50
Revert "Merge branch 'main' into issue-55-User_Identity_and_Access_Ma…
zephris Jul 18, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions .env.prod.example
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,51 @@ DJANGO_SUPERUSER_EMAIL= CHANGE THIS TO A VALID EMAIL
DJANGO_SUPERUSER_USERNAME= CHANGE THIS TO A VALID USERNAME

FRONTEND_URL="https://xxxx.codersforcauses.org/"

# KEYCLOAK
KEYCLOAK_HTTP_PORT=8080
KEYCLOAK_MANAGEMENT_PORT=9000
KEYCLOAK_IMPORT_REALM_ON_STARTUP=false

KC_BOOTSTRAP_ADMIN_USERNAME=admin
KC_BOOTSTRAP_ADMIN_PASSWORD= CHANGE THIS TO A RANDOM STRING
KC_DB=postgres
KC_DB_URL=jdbc:postgresql://db:5432/postgres
KC_DB_USERNAME=postgres
KC_DB_PASSWORD=password
KC_HOSTNAME=auth.example.com
KC_HTTP_ENABLED=true
KC_PROXY_HEADERS=xforwarded
KC_HEALTH_ENABLED=true
KC_METRICS_ENABLED=true

KEYCLOAK_BASE_URL=https://auth.example.com
KEYCLOAK_REALM=agronomy-club
KEYCLOAK_ISSUER=https://auth.example.com/realms/agronomy-club
KEYCLOAK_JWKS_URL=https://auth.example.com/realms/agronomy-club/protocol/openid-connect/certs
KEYCLOAK_AUTH_URL=https://auth.example.com/realms/agronomy-club/protocol/openid-connect/auth
KEYCLOAK_TOKEN_URL=https://auth.example.com/realms/agronomy-club/protocol/openid-connect/token
KEYCLOAK_USERINFO_URL=https://auth.example.com/realms/agronomy-club/protocol/openid-connect/userinfo

DJANGO_OIDC_CLIENT_ID=django-admin
DJANGO_OIDC_CLIENT_SECRET= CHANGE THIS TO A RANDOM STRING
FIRST_PLATFORM_ADMIN_USERNAME=admin
FIRST_PLATFORM_ADMIN_EMAIL=admin@example.com
FIRST_PLATFORM_ADMIN_PASSWORD= CHANGE THIS TO A RANDOM STRING
FIRST_DJANGO_ADMIN_USERNAME=admin
FIRST_DJANGO_ADMIN_EMAIL=admin@example.com
FIRST_DJANGO_ADMIN_PASSWORD= CHANGE THIS TO A RANDOM STRING
FIRST_KEYCLOAK_ADMIN_USERNAME=admin
FIRST_KEYCLOAK_ADMIN_PASSWORD= CHANGE THIS TO A RANDOM STRING
KEYCLOAK_SYNC_EFFECTIVE_ROLES=true
KEYCLOAK_ADMIN_REALM=master
KEYCLOAK_ADMIN_CLIENT_ID=admin-cli
KEYCLOAK_ADMIN_CLIENT_SECRET=
KEYCLOAK_ADMIN_USERNAME=admin
KEYCLOAK_ADMIN_PASSWORD= CHANGE THIS TO A RANDOM STRING
KEYCLOAK_TEST_ADMIN_EMAIL=admin@example.com
KEYCLOAK_TEST_ADMIN_USERNAME=admin
KEYCLOAK_TEST_ADMIN_PASSWORD= CHANGE THIS TO A RANDOM STRING
NEXT_PUBLIC_KEYCLOAK_URL=https://auth.example.com
NEXT_PUBLIC_KEYCLOAK_REALM=agronomy-club
NEXT_PUBLIC_KEYCLOAK_CLIENT_ID=agronomy-web
132 changes: 132 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -199,8 +199,140 @@ python manage.py migrate # apply migrations

If you run into migration conflicts that you can't be bothered to fix, run `nuke.sh` to clear your database. Then, run migrations again.

### User Self-Service Auth API

The backend provides frontend-facing endpoints for normal user signup and login.

Base URL (dev): `http://localhost:8000/api`

1. Signup

Endpoint:
`POST /auth/signup/`

Request body:
```json
{
"full_name": "Jane Example",
"grad_yr": 2031,
"discipline": "Agronomy",
"email": "jane@example.com",
"password": "StrongPass#2026"
}
```

Success response (`201 Created`):
```json
{
"message": "Signup successful.",
"user": {
"id": 1,
"full_name": "Jane Example",
"grad_yr": 2031,
"discipline": "Agronomy",
"email": "jane@example.com",
"global_role": "user"
}
}
```

Common error responses:
- `400 Bad Request` for invalid payload, weak/short password, duplicate email, or invalid graduation year.
Comment thread
zephris marked this conversation as resolved.

2. Login

Endpoint:
`POST /auth/login/`

Request body:
```json
{
"email": "jane@example.com",
"password": "StrongPass#2026"
}
```

Success response (`200 OK`):
```json
{
"message": "Login successful.",
"user": {
"id": 1,
"full_name": "Jane Example",
"grad_yr": 2031,
"discipline": "Agronomy",
"email": "jane@example.com",
"global_role": "user"
}
}
```

Common error responses:
- `401 Unauthorized` when email/password is incorrect.
- `400 Bad Request` for malformed payload.

Notes:
- These endpoints are intended for normal users (not Django admin/Keycloak admin).
- Passwords are stored as hashes in backend storage.

## Other

### Keycloak Deployment

This repository now includes a Keycloak deployment path and a realm skeleton.

1. Bootstrap environment variables (creates missing values and secrets):
```bash
./scripts/bootstrap-keycloak-env.sh ./.env
```

2. Start dev docker services (db + keycloak):
```bash
docker compose -f docker-compose.yml up -d
```

3. One-command helper (bootstraps env, copies realm skeleton into import folder, starts compose):
```bash
./scripts/deploy-with-keycloak.sh
```

First login credentials are unified as one account for both Keycloak and Django admin (set these in root `.env`):

- `FIRST_PLATFORM_ADMIN_USERNAME`
- `FIRST_PLATFORM_ADMIN_EMAIL`
- `FIRST_PLATFORM_ADMIN_PASSWORD`

This same account is used for:
- Keycloak admin panel (`http://localhost:8080/admin`)
- Django admin panel with Keycloak auth (`http://localhost:8000/admin`)

These are propagated by `./scripts/bootstrap-keycloak-env.sh` into:
- `KC_BOOTSTRAP_ADMIN_*` and `KEYCLOAK_ADMIN_*` for Keycloak admin API/auth
- `KEYCLOAK_TEST_ADMIN_*` (and `DJANGO_SUPERUSER_*`) for Django admin test/bootstrap login
- `FIRST_KEYCLOAK_ADMIN_*` and `FIRST_DJANGO_ADMIN_*` as synchronized aliases

Legacy variables `FIRST_KEYCLOAK_ADMIN_*` and `FIRST_DJANGO_ADMIN_*` are still supported as fallbacks for backwards compatibility.

To force an already-running Keycloak instance to use your configured first admin password:
```bash
./scripts/sync-keycloak-first-admin-password.sh ./.env
```

`./scripts/deploy-with-keycloak.sh` runs this sync automatically after Keycloak starts.

4. Production helper:
```bash
./scripts/deploy-with-keycloak.sh --prod
```

Realm skeleton source:
- `keycloak/agronomy-club-realm-skeleton.json`

Realm import path used by docker compose:
- `keycloak/import/agronomy-club-realm-skeleton.json`

Note: chapter access scope groups are templated with `__CHAPTER_ID__` and should be generated for real chapter IDs.

### Update Dependencies

You can run `npm install` and `poetry install` in the respective `client` and `server` folders to install the newest dependencies.
Expand Down
5 changes: 4 additions & 1 deletion client/.env.example
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
REACT_EDITOR=code

APP_ENV=DEVELOPMENT
NEXT_PUBLIC_BACKEND_URL="http://localhost:8000/api"
NEXT_PUBLIC_BACKEND_URL="http://localhost:8000/api"
NEXT_PUBLIC_KEYCLOAK_URL="http://localhost:8080"
NEXT_PUBLIC_KEYCLOAK_REALM="agronomy-club"
NEXT_PUBLIC_KEYCLOAK_CLIENT_ID="agronomy-web"
46 changes: 7 additions & 39 deletions client/src/app/about/page.tsx
Original file line number Diff line number Diff line change
@@ -1,28 +1,21 @@
import type { Metadata } from "next";
import Link from "next/link";

import { CommitteeMemberCard } from "@/components/committee-member-card";

export const metadata: Metadata = {
title: "About | Agronomy Club",
description:
"Discover the purpose, values, and impact strategy guiding the Agronomy Club.",
};
const committeeMembers = [
{ name: "Member 1", position: "President", photo: "" },
{ name: "Member 2", position: "Vice-President", photo: "" },
{ name: "Member 3", position: "Secretary", photo: "" },
{ name: "Member 4", position: "Treasurer", photo: "" },
];

export default function AboutPage() {
return (
<section className="mx-auto max-w-5xl px-4 py-16 sm:px-6 lg:px-8">
<div className="space-y-6 text-gray-700">
<div className="space-y-6">
<div>
<p className="text-sm uppercase tracking-widest text-green-600">
<p className="text-sm uppercase tracking-widest text-brand-green-dark">
Our purpose
</p>
<h1 className="mt-2 text-4xl font-bold text-gray-900">
<h1 className="mt-2 text-4xl font-bold">
Growing resilient food systems through collaboration
</h1>
<p className="mt-4 text-lg leading-relaxed">
Expand All @@ -32,11 +25,11 @@ export default function AboutPage() {
accelerate sustainable agricultural impact.
</p>
</div>
<div className="rounded-xl border border-green-100 bg-white p-6 shadow-sm">
<h2 className="text-xl font-semibold text-gray-900">Our pillars</h2>
<div className="rounded-xl border border-brand-green-light bg-brand-surface p-6 shadow-sm">
<h2 className="text-xl font-semibold">Our Pillars</h2>
<ul className="mt-4 space-y-3 text-sm">
<li>
⚙️ Research Innovation — Translating agronomic science into
⚙️ Research &amp; Innovation — Translating agronomic science into
applied solutions.
</li>
<li>
Expand All @@ -53,31 +46,6 @@ export default function AboutPage() {
</li>
</ul>
</div>
<div>
<p className="text-sm uppercase tracking-widest text-green-600">
The Committee
</p>
{/*<h2 className = "text-2xl font-bold text-gray-900">The Committee</h2>*/}
<div className="rounded-xl border border-green-100 bg-white p-4 shadow-sm">
<h2 className="text-brand-700 px-4 text-lg font-semibold">
Current Agronomy Club Committee
</h2>
<p className="text-soil-900 mt-1 px-4 font-semibold">
Meet the current committee supporting the club, its members, and
student-led activities.
</p>
<div className="mt-6 flex justify-evenly">
{committeeMembers.map((member) => (
<CommitteeMemberCard
key={member.name}
name={member.name}
position={member.position}
photo={member.photo}
/>
))}
</div>
</div>
</div>
</div>
</section>
);
Expand Down
Loading
Loading