devise-api 0.2.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.claude/commands/commit.md +38 -0
- data/.claude/commands/create-branch.md +57 -0
- data/.claude/commands/create-pr.md +41 -0
- data/CHANGELOG.md +60 -1
- data/CLAUDE.md +49 -0
- data/Gemfile +9 -3
- data/Gemfile.lock +257 -185
- data/README.md +320 -162
- data/Rakefile +6 -0
- data/app/controllers/devise/api/tokens_controller.rb +48 -108
- data/app/services/devise/api/resource_owner_service/authenticate.rb +9 -2
- data/app/services/devise/api/tokens_service/create.rb +16 -8
- data/app/services/devise/api/tokens_service/refresh.rb +17 -2
- data/app/services/devise/api/tokens_service/revoke.rb +1 -1
- data/config/locales/en.yml +1 -0
- data/docs/README.md +32 -0
- data/docs/analysis/known-issues.md +56 -0
- data/docs/analysis/security-review.md +53 -0
- data/docs/api-reference.md +95 -0
- data/docs/architecture.md +145 -0
- data/docs/configuration.md +84 -0
- data/docs/data-model.md +80 -0
- data/docs/development.md +52 -0
- data/docs/extending.md +75 -0
- data/docs/services.md +72 -0
- data/docs/testing.md +65 -0
- data/lib/devise/api/configuration.rb +7 -0
- data/lib/devise/api/controllers/helpers.rb +5 -3
- data/lib/devise/api/generators/templates/migration.rb.erb +2 -2
- data/lib/devise/api/rails/engine.rb +6 -0
- data/lib/devise/api/responses/error_response.rb +40 -29
- data/lib/devise/api/responses/token_response.rb +5 -1
- data/lib/devise/api/token.rb +34 -2
- data/lib/devise/api/version.rb +1 -1
- metadata +18 -7
- data/sig/devise/api.rbs +0 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: dbedeb52a2b5bb590f6d51d4ecb3b2c9e388b08d891b2beaebbdd51bb66f5bb9
|
|
4
|
+
data.tar.gz: 5cff467fc2cc283761df6f76bacb888fe6fa2db51563fc371a5a06571e7a926a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a1bcc2920807419c4e912083b1d4e0769ccf2dbb007b6955c2d7ad68d924c6b4743cc8466956ced069a452e13c34be64a84a2cbfbd9819eb31486c61c258e6cf
|
|
7
|
+
data.tar.gz: c1778feed8b94d4cfb77d22f223c6197496298ba745f939608cb39457615dcdca227dd306d12053e205389d545d4c71a9078273da431550d43611da4c272e84c
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
Create a git commit for the current staged/unstaged changes.
|
|
2
|
+
|
|
3
|
+
## Steps
|
|
4
|
+
|
|
5
|
+
1. Run `git status` and `git diff` to review all changes
|
|
6
|
+
2. Run `git log --oneline -5` to match the project's recent commit style
|
|
7
|
+
3. Stage only relevant files — never stage `.env`, `*.pem`, credentials, or build artifacts (`*.gem`, `pkg/`)
|
|
8
|
+
4. Draft a commit message following the format below
|
|
9
|
+
5. Create the commit
|
|
10
|
+
|
|
11
|
+
## Commit Message Format
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
<type>(<scope>): <description>
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
**Types:** `feat` | `fix` | `refactor` | `chore` | `docs` | `test` | `perf`
|
|
18
|
+
|
|
19
|
+
**Scopes** (lowercase, optional — this repo's history mostly omits the scope; use one only when it adds clarity): `tokens` | `services` | `responses` | `config` | `model` | `routes` | `generators` | `locales` | `specs` | `docs` | `ci`
|
|
20
|
+
|
|
21
|
+
**Rules:**
|
|
22
|
+
- Description explains WHY, not WHAT
|
|
23
|
+
- Use present tense ("add", not "added")
|
|
24
|
+
- If changes span multiple areas, omit the scope
|
|
25
|
+
- Keep under 72 characters
|
|
26
|
+
|
|
27
|
+
## Examples
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
feat(tokens): add token rotation on refresh to block replay
|
|
31
|
+
fix(responses): correct translation key for unconfirmed signup message
|
|
32
|
+
refactor(services): extract error rendering shared by controller actions
|
|
33
|
+
chore(ci): add Ruby 3.3 to the test matrix
|
|
34
|
+
test(specs): cover non-default authorization locations
|
|
35
|
+
docs: document refresh chain semantics in data-model
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
$ARGUMENTS
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
Create a new git branch following the project's naming conventions.
|
|
2
|
+
|
|
3
|
+
## Branch Naming Format
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
{type}/{description}
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## Types
|
|
10
|
+
|
|
11
|
+
| Type | Use Case |
|
|
12
|
+
|------|----------|
|
|
13
|
+
| `feature` | New functionality, endpoints, entities |
|
|
14
|
+
| `fix` | Bug fixes, issue corrections |
|
|
15
|
+
| `chore` | Maintenance, config, non-feature changes |
|
|
16
|
+
| `refactor` | Code restructuring without behavior change |
|
|
17
|
+
| `ci` | CI/CD pipeline changes |
|
|
18
|
+
| `test` | Test additions or modifications |
|
|
19
|
+
| `docs` | Documentation only changes |
|
|
20
|
+
| `hotfix` | Urgent production fixes |
|
|
21
|
+
|
|
22
|
+
## Description Rules
|
|
23
|
+
|
|
24
|
+
1. Use **kebab-case** (words separated by hyphens)
|
|
25
|
+
2. Start with a **verb**: `add-`, `fix-`, `update-`, `remove-`, `complete-`, `enable-`, `change-`
|
|
26
|
+
3. **Lowercase only** — no uppercase letters
|
|
27
|
+
4. Be descriptive but concise
|
|
28
|
+
|
|
29
|
+
## Workflow
|
|
30
|
+
|
|
31
|
+
1. Infer the branch type from context (default to `feature` for new functionality)
|
|
32
|
+
2. Generate a descriptive branch name based on: $ARGUMENTS (or the work done in the session)
|
|
33
|
+
3. **Immediately create and checkout the branch** — no confirmation needed:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
git checkout -b {type}/{description}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
4. Report the branch name to the user after creation
|
|
40
|
+
|
|
41
|
+
## Examples
|
|
42
|
+
|
|
43
|
+
| Description | Branch Name |
|
|
44
|
+
|-------------|-------------|
|
|
45
|
+
| Adding email notifications | `feature/add-email-notification-structure` |
|
|
46
|
+
| Fixing login timeout | `fix/login-timeout-issue` |
|
|
47
|
+
| Updating Redis config | `chore/update-redis-configuration` |
|
|
48
|
+
| Refactoring ticket service | `refactor/ticket-service` |
|
|
49
|
+
| Adding user repo tests | `test/add-user-repository-tests` |
|
|
50
|
+
|
|
51
|
+
## Anti-patterns
|
|
52
|
+
|
|
53
|
+
- No uppercase: `Feature/Add-Users`
|
|
54
|
+
- No underscores: `feature_add_users`
|
|
55
|
+
- No camelCase: `feature/AddUserEndpoint`
|
|
56
|
+
- Missing type prefix: `add-users`
|
|
57
|
+
- Too vague: `feature/users` (no verb)
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
Analyze all commits on the current branch (compared to main) and create a comprehensive PR.
|
|
2
|
+
|
|
3
|
+
## Steps
|
|
4
|
+
|
|
5
|
+
1. Run `git log main..HEAD` and `git diff main...HEAD` to understand all changes
|
|
6
|
+
2. Generate PR title in conventional format: `<type>(<scope>): <description>`
|
|
7
|
+
3. Push the branch to remote if not already pushed
|
|
8
|
+
4. Create PR using GitHub CLI:
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
gh pr create --title "<title>" --body "<body>"
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## PR Body Template
|
|
15
|
+
|
|
16
|
+
```markdown
|
|
17
|
+
## Summary
|
|
18
|
+
- [1-3 bullet points describing what changed and why]
|
|
19
|
+
|
|
20
|
+
## Changes
|
|
21
|
+
- [List of significant code changes by file/area]
|
|
22
|
+
|
|
23
|
+
## Architecture Impact
|
|
24
|
+
- [Any cross-service changes, new events, DB migrations]
|
|
25
|
+
|
|
26
|
+
## Test Plan
|
|
27
|
+
- [ ] Unit tests added/updated
|
|
28
|
+
- [ ] Integration tests pass
|
|
29
|
+
- [ ] Manual testing completed
|
|
30
|
+
- [ ] Migration tested on clean DB
|
|
31
|
+
|
|
32
|
+
## Related
|
|
33
|
+
- Closes #issue-number (if applicable)
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Rules
|
|
37
|
+
|
|
38
|
+
- Never include credentials or secrets in PR description
|
|
39
|
+
- Link related issues with `Closes #N` syntax
|
|
40
|
+
- If migration exists, mention it explicitly in Architecture Impact
|
|
41
|
+
- Scope: $ARGUMENTS (optional: specific context or issue number)
|
data/CHANGELOG.md
CHANGED
|
@@ -1,4 +1,63 @@
|
|
|
1
|
-
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## [0.3.1] - 2026-08-25
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
- Upgraded the development/test dependency tree to current stable versions (Rails 8.1, devise 5.0, puma 7, sqlite3 2, rspec-rails 8, rubocop 1.90), resolving the open Dependabot alerts; runtime gemspec constraints for host apps are unchanged (Ruby >= 2.7, Rails >= 6.0) (#58)
|
|
7
|
+
- CI now tests against Ruby 3.2/3.3/3.4/4.0 (Rails 8 requires Ruby >= 3.2)
|
|
8
|
+
|
|
9
|
+
## [0.3.0] - 2026-08-25
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
- Refresh token rotation with reuse detection behind `refresh_token.rotation_enabled` (default `false`): each refresh revokes the presented refresh token, and presenting an already-rotated/revoked refresh token revokes the whole token family (SEC-2)
|
|
13
|
+
- Paranoid mode behind `paranoid` (default `false`): unknown accounts and wrong passwords both return the generic `invalid_authentication` error with no lockable/confirmable details, preventing account enumeration (SEC-5)
|
|
14
|
+
- `error_response.verbose_account_state` (default `true`): set to `false` to omit the `lockable`/`confirmable` metadata blocks from error responses (SEC-7)
|
|
15
|
+
- `Devise::Api::Token#revoke!` and `#revoke_family!` helpers
|
|
16
|
+
- New `invalid_login` error (HTTP 400) returned instead of `invalid_email` when the model's `authentication_keys` do not include `:email`
|
|
17
|
+
- Lockable error responses now include the correctly spelled `failed_attempts` key alongside the deprecated `failed_attemps` (the misspelling will be removed in the next major release)
|
|
18
|
+
- `access_token`, `refresh_token` and `previous_refresh_token` are added to the host app's `filter_parameters`, and the token model filters them from `#inspect` output (GH-51)
|
|
19
|
+
|
|
20
|
+
### Changed
|
|
21
|
+
- **Breaking-ish:** `POST /<scope>/tokens/refresh` with an unknown refresh token now returns `invalid_refresh_token` (HTTP 400) instead of `invalid_token` (HTTP 401)
|
|
22
|
+
- The install generator's migration now creates **unique** indexes on `access_token` and `refresh_token`; token creation rescues `ActiveRecord::RecordNotUnique` and retries with a fresh token (SEC-4). Existing installs should add a migration:
|
|
23
|
+
```ruby
|
|
24
|
+
remove_index :devise_api_tokens, :access_token
|
|
25
|
+
remove_index :devise_api_tokens, :refresh_token
|
|
26
|
+
add_index :devise_api_tokens, :access_token, unique: true
|
|
27
|
+
add_index :devise_api_tokens, :refresh_token, unique: true
|
|
28
|
+
```
|
|
29
|
+
- `current_devise_api_refresh_token` is now memoized in the shared controller helpers (the duplicate controller-level override was removed)
|
|
30
|
+
- Internal time handling standardized on `Time.current`
|
|
31
|
+
|
|
32
|
+
### Removed
|
|
33
|
+
- Vestigial RBS stub (`sig/devise/api.rbs`)
|
|
34
|
+
|
|
35
|
+
## [0.2.0] - 2024-09-27
|
|
36
|
+
|
|
37
|
+
- Resource lookup uses the model's `authentication_keys` instead of hardcoding `email` (#46)
|
|
38
|
+
- Fixed nil memoization of `current_devise_api_token` / `current_devise_api_refresh_token` (#48)
|
|
39
|
+
- Fixed the translation key for the unconfirmed signup message (#49)
|
|
40
|
+
|
|
41
|
+
## [0.1.3] - 2023-08-08
|
|
42
|
+
|
|
43
|
+
- Fixed `AbstractController::DoubleRenderError` on refresh (#29)
|
|
44
|
+
- Allowed defining extra fields for sign up via `sign_up.extra_fields` (#36, #38)
|
|
45
|
+
- Disabled parameter wrapping in `TokensController` (#42)
|
|
46
|
+
|
|
47
|
+
## [0.1.2] - 2023-05-30
|
|
48
|
+
|
|
49
|
+
- Added `sign_up.enabled` option to disable the sign up endpoint (#15)
|
|
50
|
+
- Fixed refresh behavior (#14)
|
|
51
|
+
- Fixed undefined variable error in the controller helper (#25)
|
|
52
|
+
- Migration template respects the configured primary/foreign key types (#23)
|
|
53
|
+
|
|
54
|
+
## [0.1.1] - 2023-01-14
|
|
55
|
+
|
|
56
|
+
- Fixed invalid strategy error (#2)
|
|
57
|
+
|
|
58
|
+
## [0.1.0] - 2023-01-14
|
|
59
|
+
|
|
60
|
+
- First public release: `:api` Devise module with token sign up / sign in / refresh / revoke / info endpoints (#1)
|
|
2
61
|
|
|
3
62
|
## [0.0.0] - 2023-01-09
|
|
4
63
|
|
data/CLAUDE.md
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
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
|
+
`devise-api` is a Rails engine gem that adds token-based API authentication (access tokens + refresh tokens) to Devise. It registers an `:api` Devise module that models opt into via `devise :api`. Supports Ruby >= 2.7, Rails >= 6.0.
|
|
8
|
+
|
|
9
|
+
## Documentation
|
|
10
|
+
|
|
11
|
+
`docs/` holds the detailed internal documentation — start at `docs/README.md` (index + ground rules for working in this repo). Highlights:
|
|
12
|
+
|
|
13
|
+
- `docs/architecture.md` — components, boot sequence, request lifecycle (with diagrams)
|
|
14
|
+
- `docs/api-reference.md` — endpoints, payloads, full error catalog
|
|
15
|
+
- `docs/configuration.md`, `docs/data-model.md`, `docs/services.md`, `docs/extending.md`, `docs/testing.md`, `docs/development.md`
|
|
16
|
+
- `docs/analysis/security-review.md` and `docs/analysis/known-issues.md` — the vetted backlog of security findings (SEC-*) and code-quality issues (KI-*). **Consult these before changing surprising code** (some quirks, like the `failed_attemps` response-field typo, are shipped public API), and update them when you fix an item.
|
|
17
|
+
|
|
18
|
+
Docs are contractual: a PR that changes behavior described in `docs/` must update the matching document.
|
|
19
|
+
|
|
20
|
+
## Commands
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
bundle install # install dependencies
|
|
24
|
+
bundle exec rake # default task: rspec + rubocop (what CI runs)
|
|
25
|
+
bundle exec rake rspec # run the full test suite
|
|
26
|
+
bundle exec rspec spec/services/tokens_service/refresh_spec.rb # run one spec file
|
|
27
|
+
bundle exec rspec spec/services/tokens_service/refresh_spec.rb:12 # run one example by line
|
|
28
|
+
bundle exec rubocop # lint (config in .rubocop.yml, single quotes, 120-char lines)
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Tests run against the dummy Rails app in `spec/dummy` (sqlite3, schema in `spec/dummy/db/schema.rb`). `spec/spec_helper.rb` boots it via `require 'dummy/config/environment'`; DatabaseCleaner wraps each example. If token/schema fields change, the dummy app's migrations and schema must be updated alongside the generator template.
|
|
32
|
+
|
|
33
|
+
## Architecture
|
|
34
|
+
|
|
35
|
+
Everything is configured through a single global: `Devise.api.config` (a `Dry::Configurable` instance defined in `lib/devise/api/configuration.rb`, installed as `Devise.api` in `lib/devise/api.rb`). Config includes token expiry/generators, sign_up enablement/extra_fields, authorization header/params location, before/after callbacks for each action, and — importantly — `base_token_model` and `base_controller`, which are **string class names resolved with `constantize` at use sites** so host apps can substitute their own subclasses. When touching model/controller references, use the config values, never hardcode `Devise::Api::Token`.
|
|
36
|
+
|
|
37
|
+
Flow of a request:
|
|
38
|
+
|
|
39
|
+
1. **Routes** — `lib/devise/api/rails/routes.rb` monkey-patches `ActionDispatch::Routing::Mapper#devise_api`, which `devise_for` invokes because `lib/devise/api.rb` calls `Devise.add_module :api, route: {...}`. Draws POST `sign_up`/`sign_in`/`revoke`/`refresh` and GET `info` under `/<scope>/tokens`.
|
|
40
|
+
2. **Controller** — `app/controllers/devise/api/tokens_controller.rb` (inherits from configurable `base_controller`, default `::DeviseController`). Each action runs the configured `before_*` callback, delegates to a service, then renders a `TokenResponse` or `ErrorResponse` and fires the `after_successful_*` callback.
|
|
41
|
+
3. **Services** — `app/services/devise/api/**`, all inheriting `Devise::Api::BaseService` which wires up dry-initializer (`option :x, type: Types::...`) and dry-monads (`Success`/`Failure` + do-notation `yield`). Two namespaces: `ResourceOwnerService` (`Authenticate`, `SignIn`, `SignUp`) and `TokensService` (`Create`, `Refresh`, `Revoke`). Services compose: e.g. `SignIn` yields `Authenticate` then `TokensService::Create`. Failures are hashes like `{ error: :invalid_authentication, record: ... }` splatted into `ErrorResponse`.
|
|
42
|
+
4. **Responses** — `lib/devise/api/responses/`. `ErrorResponse` maps a symbolic error type (see its `ERROR_TYPES` list) to an i18n message (`config/locales/en.yml`, keys under `devise.api.responses`) and HTTP status. `TokenResponse` shapes the JSON per action.
|
|
43
|
+
|
|
44
|
+
Supporting pieces:
|
|
45
|
+
|
|
46
|
+
- **Token model** — `lib/devise/api/token.rb` (`devise_api_tokens` table, polymorphic `resource_owner`). Knows `expired?`/`revoked?`/`active?` and generates unique tokens via the configured generator procs. Refresh chains are linked through `previous_refresh_token`.
|
|
47
|
+
- **Controller helpers** — `lib/devise/api/controllers/helpers.rb` is included into all of ActionController on load: `authenticate_devise_api_token!`, `current_devise_api_token`, `current_devise_api_user`. Token extraction honors `authorization.location` (`:header`, `:params`, `:both`).
|
|
48
|
+
- **Devise module** — `Devise::Models::Api` in `lib/devise/api.rb` adds the `access_tokens` association and `supported_devise_modules`; the codebase checks that inquiry to conditionally support optional Devise modules (trackable, lockable, confirmable).
|
|
49
|
+
- **Generator** — `rails generate devise_api:install` (`lib/devise/api/generators/install_generator.rb`) copies the migration template and locale file into the host app.
|
data/Gemfile
CHANGED
|
@@ -25,10 +25,10 @@ gem 'pry', '~> 0.14.1'
|
|
|
25
25
|
gem 'sprockets-rails'
|
|
26
26
|
|
|
27
27
|
# Use sqlite3 as the database for Active Record
|
|
28
|
-
gem 'sqlite3', '~>
|
|
28
|
+
gem 'sqlite3', '~> 2.0'
|
|
29
29
|
|
|
30
30
|
# Use the Puma web server [https://github.com/puma/puma]
|
|
31
|
-
gem 'puma', '~>
|
|
31
|
+
gem 'puma', '~> 7.0', '>= 7.2.1'
|
|
32
32
|
|
|
33
33
|
# A library for setting up Ruby objects as test data [https://github.com/thoughtbot/factory_bot]
|
|
34
34
|
gem 'factory_bot', '~> 6.2', '>= 6.2.1'
|
|
@@ -40,10 +40,16 @@ gem 'faker', '~> 3.1'
|
|
|
40
40
|
gem 'database_cleaner', '~> 2.0', '>= 2.0.1'
|
|
41
41
|
|
|
42
42
|
# RSpec for Rails 5+ [https://github.com/rspec/rspec-rails]
|
|
43
|
-
gem 'rspec-rails', '~>
|
|
43
|
+
gem 'rspec-rails', '~> 8.0'
|
|
44
44
|
|
|
45
45
|
# RSpec runner and formatters [https://github.com/rspec/rspec-core]
|
|
46
46
|
gem 'rspec-core'
|
|
47
47
|
|
|
48
48
|
# Common code needed by the other RSpec gems. Not intended for direct use [https://github.com/rspec/rspec-support]
|
|
49
49
|
gem 'rspec-support'
|
|
50
|
+
|
|
51
|
+
# Code coverage analysis tool for Ruby [https://github.com/simplecov-ruby/simplecov]
|
|
52
|
+
gem 'simplecov', '~> 0.22', require: false
|
|
53
|
+
|
|
54
|
+
# OpenStruct implementation, no longer a default gem since Ruby 3.5 [https://github.com/ruby/ostruct]
|
|
55
|
+
gem 'ostruct'
|