|
| 1 | +# CLAUDE.md |
| 2 | + |
| 3 | +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. |
| 4 | + |
| 5 | +## Project Overview |
| 6 | + |
| 7 | +**Oauth2id** is a production-ready Rails 7.2.2 Single Sign-On (SSO) Portal implementing OAuth2, OpenID Connect, and SAML 2.0 authentication protocols. It's a multi-protocol identity provider suitable for enterprise authentication scenarios. |
| 8 | + |
| 9 | +### Key Technologies |
| 10 | +- **Backend**: Ruby 3.0+ on Rails 7.2.2 with Puma |
| 11 | +- **Database**: SQLite (default) - configurable to PostgreSQL/MySQL |
| 12 | +- **Frontend**: Stimulus controllers + Webpacker 5.4.4 + Bootstrap 4 (Vali Admin theme) |
| 13 | +- **Authentication**: Devise + devise-jwt, Doorkeeper, doorkeeper-openid_connect, SAML IdP |
| 14 | +- **Package Manager**: pnpm for Node.js dependencies |
| 15 | + |
| 16 | +## Architecture Overview |
| 17 | + |
| 18 | +### MVC Rails Application Structure |
| 19 | +``` |
| 20 | +app/ |
| 21 | + controllers/ # Rails controllers |
| 22 | + models/ # ActiveRecord models (User, Doorkeeper::Application, Department, Position, Profile, Jwt) |
| 23 | + views/ # ERB templates |
| 24 | + javascript/ # Stimulus controllers + Webpack packs |
| 25 | + policies/ # Pundit authorization policies |
| 26 | + datatables/ # AJAX data tables for admin interfaces |
| 27 | +
|
| 28 | +config/ |
| 29 | + routes.rb # OAuth2/OIDC/SAML routes + user admin routes |
| 30 | + initializers/ # Devise, Doorkeeper, SAML, OpenID Connect configs |
| 31 | + environments/ # Environment-specific configs (development/test/production) |
| 32 | + credentials.yml.enc # Encrypted credentials (managed via bin/rails credentials:edit) |
| 33 | +
|
| 34 | +db/ |
| 35 | + migrate/ # Database migrations |
| 36 | + schema.rb # Current database schema |
| 37 | + seeds.rb # Seed data |
| 38 | +
|
| 39 | +test/ |
| 40 | + controllers/ # Controller tests |
| 41 | + models/ # Model tests |
| 42 | + system/ # Capybara system tests |
| 43 | + fixtures/ # Test fixtures |
| 44 | +``` |
| 45 | + |
| 46 | +### Authentication Architecture |
| 47 | + |
| 48 | +**Multi-Protocol SSO Implementation:** |
| 49 | + |
| 50 | +1. **OAuth2 Provider** (Doorkeeper) |
| 51 | + - Routes: `/oauth/authorize`, `/oauth/token`, `/oauth/authorize`, `/oauth/discovery/keys` |
| 52 | + - Controller glue: `app/controllers/doorkeeper_controller.rb` |
| 53 | + - App model: `app/models/doorkeeper_application.rb` |
| 54 | + |
| 55 | +2. **OpenID Connect** (doorkeeper-openid_connect) |
| 56 | + - Extends OAuth2 with OIDC support |
| 57 | + - Requires `openid` scope minimum |
| 58 | + - Discovery endpoint: `/oauth/discovery/keys` |
| 59 | + - Initializer: `config/initializers/doorkeeper_openid_connect.rb` |
| 60 | + |
| 61 | +3. **SAML 2.0 Identity Provider** (saml_idp) |
| 62 | + - Routes: `/saml/auth`, `/saml/metadata`, `/saml/logout` |
| 63 | + - Controller: `app/controllers/saml_idp_controller.rb` |
| 64 | + - Initializer: `config/initializers/saml_idp.rb` |
| 65 | + |
| 66 | +4. **User Authentication** (Devise) |
| 67 | + - Session-based authentication |
| 68 | + - JWT support via devise-jwt |
| 69 | + - Allowlisted JWT management: `app/models/allowlisted_jwt.rb` |
| 70 | + |
| 71 | +## Common Development Commands |
| 72 | + |
| 73 | +### Initial Setup |
| 74 | +```bash |
| 75 | +# First-time setup (installs gems, packages, prepares DB, runs seeds) |
| 76 | +bin/setup |
| 77 | + |
| 78 | +# Install frontend dependencies |
| 79 | +pnpm install --frozen-lockfile |
| 80 | +``` |
| 81 | + |
| 82 | +### Running the Application |
| 83 | +```bash |
| 84 | +# Standard Rails server |
| 85 | +bin/rails s |
| 86 | + |
| 87 | +# Hot asset reloading (two terminals) |
| 88 | +bin/rails s # Terminal 1 |
| 89 | +bin/webpack-dev-server # Terminal 2 |
| 90 | + |
| 91 | +# Or use Foreman (both processes) |
| 92 | +foreman start -f Procfile.dev |
| 93 | + |
| 94 | +# Access the application |
| 95 | +# Development: http://localhost:3000 |
| 96 | +# HTTPS local (with puma-dev): https://oauth2id.test |
| 97 | +``` |
| 98 | + |
| 99 | +### Database Operations |
| 100 | +```bash |
| 101 | +# Create/migrate DB |
| 102 | +bin/rails db:prepare |
| 103 | + |
| 104 | +# Run migrations only |
| 105 | +bin/rails db:migrate |
| 106 | + |
| 107 | +# Load seed data |
| 108 | +bin/rails db:seed |
| 109 | + |
| 110 | +# Load fixtures |
| 111 | +bin/rails db:fixtures:load |
| 112 | +``` |
| 113 | + |
| 114 | +### Testing |
| 115 | +```bash |
| 116 | +# Run all tests |
| 117 | +bin/rails test:all |
| 118 | + |
| 119 | +# Run specific test file |
| 120 | +bin/rails test test/models/user_test.rb |
| 121 | + |
| 122 | +# Run system tests |
| 123 | +bin/rails test:system |
| 124 | + |
| 125 | +# Run tests with coverage |
| 126 | +COVERAGE=true bin/rails test |
| 127 | +``` |
| 128 | + |
| 129 | +### Linting & Formatting |
| 130 | +```bash |
| 131 | +# Ruby linting |
| 132 | +bundle exec rubocop |
| 133 | + |
| 134 | +# Fix Ruby linting issues |
| 135 | +bundle exec rubocop -A |
| 136 | + |
| 137 | +# JavaScript linting |
| 138 | +npx eslint app/javascript/ |
| 139 | +``` |
| 140 | + |
| 141 | +### Asset Management |
| 142 | +```bash |
| 143 | +# Compile assets for production |
| 144 | +bin/rails assets:precompile |
| 145 | + |
| 146 | +# Build Webpack bundles |
| 147 | +bin/webpack |
| 148 | + |
| 149 | +# Watch mode for asset compilation |
| 150 | +bin/webpack --watch |
| 151 | +``` |
| 152 | + |
| 153 | +### Docker Operations |
| 154 | +```bash |
| 155 | +# Build image |
| 156 | +docker build --tag ericguo/oauth2id:main . |
| 157 | + |
| 158 | +# Run container |
| 159 | +docker run -p 3000:3000 -d --restart always --name oauth2id \ |
| 160 | + --env RAILS_MASTER_KEY=YourMasterKey \ |
| 161 | + -v ./storage:/rails/storage \ |
| 162 | + ericguo/oauth2id:main |
| 163 | + |
| 164 | +# Debug container |
| 165 | +docker run --env RAILS_MASTER_KEY=YourMasterKey \ |
| 166 | + -v ./storage:/rails/storage \ |
| 167 | + -it ericguo/oauth2id:main bash |
| 168 | + |
| 169 | +# Push to registry |
| 170 | +docker push ericguo/oauth2id:main |
| 171 | +``` |
| 172 | + |
| 173 | +### Deployment (Capistrano) |
| 174 | +```bash |
| 175 | +# Deploy to production |
| 176 | +cap production deploy |
| 177 | + |
| 178 | +# Run migrations in production |
| 179 | +cap production deploy:migrate |
| 180 | +``` |
| 181 | + |
| 182 | +### Key Generation |
| 183 | + |
| 184 | +#### OIDC Keys (Required for OpenID Connect) |
| 185 | +```bash |
| 186 | +openssl genpkey -algorithm RSA -out oauth2id_oidc_private_key.pem -pkeyopt rsa_keygen_bits:2048 |
| 187 | +openssl rsa -pubout -in oauth2id_oidc_private_key.pem -out oauth2id_oidc_public_key.pem |
| 188 | +# Public key available at: /oauth/discovery/keys |
| 189 | +``` |
| 190 | + |
| 191 | +#### SAML 2.0 Keys |
| 192 | +```bash |
| 193 | +openssl req -x509 -sha256 -nodes -days 3650 -newkey rsa:2048 \ |
| 194 | + -keyout oauth2id_saml_key.key -out oauth2id_saml_cert.crt |
| 195 | +# View fingerprint: |
| 196 | +openssl x509 -in oauth2id_saml_cert.crt -noout -sha256 -fingerprint |
| 197 | +``` |
| 198 | + |
| 199 | +#### JWT RS256 Keys |
| 200 | +```bash |
| 201 | +openssl genpkey -algorithm RSA -out oauth2id_jwt_private_key.pem -pkeyopt rsa_keygen_bits:2048 |
| 202 | +openssl rsa -pubout -in oauth2id_jwt_private_key.pem -out oauth2id_jwt_public_key.pem |
| 203 | +``` |
| 204 | + |
| 205 | +## Development Patterns & Conventions |
| 206 | + |
| 207 | +### Rails Conventions |
| 208 | +- **Strong Parameters**: Permit attributes in controllers following existing patterns |
| 209 | +- **Authorization**: Use Pundit policies in `app/policies/*` with `authorize(record)` and `policy_scope(Model)` |
| 210 | +- **Authentication**: Devise handles user auth; JWT via devise-jwt and Warden strategies |
| 211 | +- **I18n**: Use `t('key')` instead of literals; add keys to `config/locales/*` |
| 212 | +- **Controllers**: Keep lean; offload logic to helpers (`app/helpers/application_helper.rb`) or presenters |
| 213 | + |
| 214 | +### Frontend (Stimulus + Webpacker) |
| 215 | +- **Entry pack**: `app/javascript/packs/application.js` |
| 216 | +- **Controllers**: Auto-loaded from `app/javascript/controllers/*` |
| 217 | +- **Naming**: Controllers named `*_controller.js`, export default class with targets/actions |
| 218 | +- **View integration**: Use `data-controller`, `data-action`, `data-target` in ERB |
| 219 | +- **Pack loading**: Include via `javascript_pack_tag 'application'` in layouts |
| 220 | +- **Auto-loading**: Controllers auto-registered via `definitionsFromContext(require.context('controllers', true, /.js$/))` |
| 221 | +- **Polyfills**: `regenerator-runtime` and `@stimulus/polyfills` included globally |
| 222 | + |
| 223 | +### DataTables |
| 224 | +- AJAX-powered data tables for admin interfaces |
| 225 | +- Keep query/filtering logic in datatable classes under `app/datatables/*` |
| 226 | +- Initialize via Ajax, respond with JSON from Datatable classes |
| 227 | +- Column mapping kept in the table class |
| 228 | + |
| 229 | +### Security |
| 230 | +- **CSRF Protection**: Always enabled for web endpoints |
| 231 | +- **API OPTIONS endpoints** (e.g., `/api/me`): Follow existing implementation in `Api::ApplicationController` |
| 232 | +- **Credentials**: Use `Rails.application.credentials` or environment variables (never hardcode secrets) |
| 233 | + |
| 234 | +### Background Jobs & Mailers |
| 235 | +- Extend `ApplicationJob` and `ApplicationMailer` base classes |
| 236 | +- Keep idempotent and side-effect aware |
| 237 | + |
| 238 | +### Testing |
| 239 | +- **Framework**: Minitest + Capybara |
| 240 | +- **System tests**: Capybara with selenium-webdriver |
| 241 | +- **Fixtures**: Prefer fixtures; avoid depending on `db/seeds.rb` for tests |
| 242 | +- **CI integration**: `minitest-ci`, `simplecov` in `:ci` gem group |
| 243 | +- Layout: `test/*` with fixtures in `test/fixtures/*` |
| 244 | + |
| 245 | +## Key Configuration Files |
| 246 | + |
| 247 | +### Core Application |
| 248 | +- `config/application.rb` - Main Rails config (i18n: zh-CN, ActiveStorage/ActionCable/ActionMailbox/ActionText disabled) |
| 249 | +- `config/routes.rb` - SSO routes (Doorkeeper, SAML) + admin routes |
| 250 | +- `config/database.yml` - Database configuration (SQLite default) |
| 251 | + |
| 252 | +### Authentication & Security |
| 253 | +- `config/initializers/devise.rb` - Devise authentication |
| 254 | +- `config/initializers/doorkeeper.rb` - OAuth2 provider config |
| 255 | +- `config/initializers/doorkeeper_openid_connect.rb` - OIDC config |
| 256 | +- `config/initializers/saml_idp.rb` - SAML IdP configuration |
| 257 | +- `config/credentials.yml.enc` - Encrypted credentials (managed via `bin/rails credentials:edit`) |
| 258 | +- `config/master.key` - Encryption key (NOT in repo!) |
| 259 | + |
| 260 | +### Frontend |
| 261 | +- `config/webpacker.yml` - Webpacker configuration |
| 262 | +- `package.json` - Node.js dependencies |
| 263 | +- `.eslintrc.js` - ESLint configuration |
| 264 | + |
| 265 | +### Deployment |
| 266 | +- `Dockerfile` - Multi-stage Docker build |
| 267 | +- `Capfile` + `config/deploy.rb` - Capistrano deployment config |
| 268 | +- `Procfile.dev` - Foreman process definitions |
| 269 | +- `.gitlab-ci.yml` - GitLab CI pipeline |
| 270 | +- `.circleci/config.yml` - CircleCI configuration |
| 271 | + |
| 272 | +## Critical Routes & Endpoints |
| 273 | + |
| 274 | +### OAuth2 / OpenID Connect |
| 275 | +- Authorization: `GET /oauth/authorize` |
| 276 | +- Token: `POST /oauth/token` |
| 277 | +- Discovery Keys: `GET /oauth/discovery/keys` |
| 278 | +- User info: `GET /oauth/userinfo` (OIDC only) |
| 279 | + |
| 280 | +### SAML 2.0 |
| 281 | +- Auth: `GET /saml/auth` |
| 282 | +- Metadata: `GET /saml/metadata` |
| 283 | +- Logout: `GET /saml/logout` |
| 284 | + |
| 285 | +### Admin & Management |
| 286 | +- Users: `/users` (Devise-managed) |
| 287 | +- Applications: `/oauth/applications` (Doorkeeper) |
| 288 | +- JWT Management: `/jwt` endpoints |
| 289 | +- Departments/Positions: `/departments`, `/positions` |
| 290 | + |
| 291 | +## Environment Setup Notes |
| 292 | + |
| 293 | +### puma-dev (HTTPS Local Development) |
| 294 | +```bash |
| 295 | +brew install puma/puma/puma/puma-dev |
| 296 | +sudo puma-dev -setup |
| 297 | +puma-dev -install |
| 298 | +cd ~/.puma-dev |
| 299 | +ln -s /path/to/oauth2id oauth2id |
| 300 | +# Visit: https://oauth2id.test |
| 301 | +``` |
| 302 | + |
| 303 | +**Known Issues:** |
| 304 | +- For newer MacOS: Add Puma-dev CA to System keychain and restart browser |
| 305 | +- For Faraday: Add CA to OpenSSL cert list using openssl-osx-ca |
| 306 | +- For httpclient: Copy cert.pem to httpclient gem directory |
| 307 | + |
| 308 | +### Database Migration (MySQL → PostgreSQL) |
| 309 | +Use mysql-postgresql-converter: |
| 310 | +```bash |
| 311 | +mysqldump --set-gtid-purged=OFF --no-tablespaces --compatible=postgresql --default-character-set=utf8 -r db.mysql -u user pass dbname |
| 312 | +python ./db_converter.py db.mysql db.psql |
| 313 | +psql -d new_db -f db.psql |
| 314 | +# Replace ' datetime(6) ' with ' timestamp(6) without time zone ' |
| 315 | +``` |
| 316 | + |
| 317 | +## Important Notes |
| 318 | + |
| 319 | +- **Storage**: SQLite databases stored in `storage/*.sqlite3` (mounted in Docker) |
| 320 | +- **UI Theme**: Vali Admin v2.4.1 based on Bootstrap 4, supports IE 11 |
| 321 | +- **Locales**: Chinese (zh-CN) configured in `config/locales/*` |
| 322 | +- **Master Key**: Required for encrypted credentials; never commit `config/master.key` |
| 323 | +- **Production Data**: Users may need `u.confirm` to enable sign-in |
0 commit comments