Skip to content

Add API token management system#344

Open
ba88im wants to merge 117 commits into
mainfrom
ba88im-superset/ba88im/api-tokens
Open

Add API token management system#344
ba88im wants to merge 117 commits into
mainfrom
ba88im-superset/ba88im/api-tokens

Conversation

@ba88im
Copy link
Copy Markdown

@ba88im ba88im commented Apr 9, 2026

Summary

  • Adds a new ApiToken model with course scoping, user association, expiration, usage tracking (last_used_at), read/write permissions, and secure token storage (SHA-256 digest, never plaintext)
  • Adds an instructor-only "API Tokens" management page (view + revoke) accessible from the course sidebar
  • Updates the CSV export endpoint to support both the new token system and the legacy readonly_api_token for backward compatibility
  • Includes a rake task (rake api_tokens:migrate_legacy_tokens) to migrate existing legacy tokens to the new system, linked to the auto-approval system user

Context

Previously, each course had a single readonly_api_token stored in plaintext on the courses table — no user association, no expiration, no management UI. This PR replaces that with a proper token system that supports multiple tokens per course, each scoped to a user with configurable permissions.

Phase 1 (this PR): model + view/delete UI + migration tooling
Phase 2 (future): token creation UI

Related: #155 (Slack approve/deny links will use read+write tokens)

Test plan

  • Run rails db:migrate and verify api_tokens table is created
  • Run bundle exec rspec spec/models/api_token_spec.rb — model tests pass
  • Run bundle exec rspec spec/controllers/api_tokens_controller_spec.rb — controller tests pass
  • Navigate to /courses/:id/api_tokens as instructor — see token table
  • Verify students cannot access the API Tokens page
  • Revoke a token and verify it shows as revoked
  • Verify CSV export still works with legacy readonly_api_token
  • Run rake api_tokens:migrate_legacy_tokens and verify tokens are migrated

dependabot Bot and others added 30 commits March 17, 2026 17:37
Bumps [puma](https://github.com/puma/puma) from 7.1.0 to 7.2.0.
- [Release notes](https://github.com/puma/puma/releases)
- [Changelog](https://github.com/puma/puma/blob/main/History.md)
- [Commits](puma/puma@v7.1.0...v7.2.0)

---
updated-dependencies:
- dependency-name: puma
  dependency-version: 7.2.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Bumps [bootsnap](https://github.com/rails/bootsnap) from 1.20.1 to 1.21.1.
- [Release notes](https://github.com/rails/bootsnap/releases)
- [Changelog](https://github.com/rails/bootsnap/blob/main/CHANGELOG.md)
- [Commits](rails/bootsnap@v1.20.1...v1.21.1)

---
updated-dependencies:
- dependency-name: bootsnap
  dependency-version: 1.21.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Bumps [pg](https://github.com/ged/ruby-pg) from 1.6.2 to 1.6.3.
- [Changelog](https://github.com/ged/ruby-pg/blob/master/CHANGELOG.md)
- [Commits](ged/ruby-pg@v1.6.2...v1.6.3)

---
updated-dependencies:
- dependency-name: pg
  dependency-version: 1.6.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Bumps [sentry-rails](https://github.com/getsentry/sentry-ruby) from 6.2.0 to 6.3.0.
- [Release notes](https://github.com/getsentry/sentry-ruby/releases)
- [Changelog](https://github.com/getsentry/sentry-ruby/blob/master/CHANGELOG.md)
- [Commits](getsentry/sentry-ruby@6.2.0...6.3.0)

---
updated-dependencies:
- dependency-name: sentry-rails
  dependency-version: 6.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Bumps [axe-core-cucumber](https://github.com/dequelabs/axe-core-gems) from 4.11.0 to 4.11.1.
- [Release notes](https://github.com/dequelabs/axe-core-gems/releases)
- [Changelog](https://github.com/dequelabs/axe-core-gems/blob/develop/CHANGELOG.md)
- [Commits](dequelabs/axe-core-gems@v4.11.0...v4.11.1)

---
updated-dependencies:
- dependency-name: axe-core-cucumber
  dependency-version: 4.11.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Bumps [bootstrap](https://github.com/twbs/bootstrap-rubygem) from 5.3.5 to 5.3.8.
- [Release notes](https://github.com/twbs/bootstrap-rubygem/releases)
- [Changelog](https://github.com/twbs/bootstrap-rubygem/blob/main/CHANGELOG.md)
- [Commits](twbs/bootstrap-rubygem@v5.3.5...v5.3.8)

---
updated-dependencies:
- dependency-name: bootstrap
  dependency-version: 5.3.8
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Bumps [turbo-rails](https://github.com/hotwired/turbo-rails) from 2.0.21 to 2.0.23.
- [Release notes](https://github.com/hotwired/turbo-rails/releases)
- [Commits](hotwired/turbo-rails@v2.0.21...v2.0.23)

---
updated-dependencies:
- dependency-name: turbo-rails
  dependency-version: 2.0.23
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Bumps [rubocop](https://github.com/rubocop/rubocop) from 1.82.1 to 1.84.2.
- [Release notes](https://github.com/rubocop/rubocop/releases)
- [Changelog](https://github.com/rubocop/rubocop/blob/master/CHANGELOG.md)
- [Commits](rubocop/rubocop@v1.82.1...v1.84.2)

---
updated-dependencies:
- dependency-name: rubocop
  dependency-version: 1.84.2
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Bumps [bootsnap](https://github.com/rails/bootsnap) from 1.21.1 to 1.23.0.
- [Release notes](https://github.com/rails/bootsnap/releases)
- [Changelog](https://github.com/rails/bootsnap/blob/main/CHANGELOG.md)
- [Commits](rails/bootsnap@v1.21.1...v1.23.0)

---
updated-dependencies:
- dependency-name: bootsnap
  dependency-version: 1.23.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Bumps [sentry-ruby](https://github.com/getsentry/sentry-ruby) from 6.3.0 to 6.3.1.
- [Release notes](https://github.com/getsentry/sentry-ruby/releases)
- [Changelog](https://github.com/getsentry/sentry-ruby/blob/master/CHANGELOG.md)
- [Commits](getsentry/sentry-ruby@6.3.0...6.3.1)

---
updated-dependencies:
- dependency-name: sentry-ruby
  dependency-version: 6.3.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Bumps [annotaterb](https://github.com/drwl/annotaterb) from 4.20.0 to 4.22.0.
- [Changelog](https://github.com/drwl/annotaterb/blob/main/CHANGELOG.md)
- [Commits](drwl/annotaterb@v4.20.0...v4.22.0)

---
updated-dependencies:
- dependency-name: annotaterb
  dependency-version: 4.22.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Bumps the bundler group with 1 update in the / directory: [faraday](https://github.com/lostisland/faraday).


Updates `faraday` from 2.14.0 to 2.14.1
- [Release notes](https://github.com/lostisland/faraday/releases)
- [Changelog](https://github.com/lostisland/faraday/blob/main/CHANGELOG.md)
- [Commits](lostisland/faraday@v2.14.0...v2.14.1)

---
updated-dependencies:
- dependency-name: faraday
  dependency-version: 2.14.1
  dependency-type: direct:production
  dependency-group: bundler
...

Signed-off-by: dependabot[bot] <support@github.com>
Bumps [brakeman](https://github.com/presidentbeef/brakeman) from 7.1.2 to 8.0.2.
- [Release notes](https://github.com/presidentbeef/brakeman/releases)
- [Changelog](https://github.com/presidentbeef/brakeman/blob/main/CHANGES.md)
- [Commits](presidentbeef/brakeman@v7.1.2...v8.0.2)

---
updated-dependencies:
- dependency-name: brakeman
  dependency-version: 8.0.2
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Bumps [selenium-webdriver](https://github.com/SeleniumHQ/selenium) from 4.40.0 to 4.41.0.
- [Release notes](https://github.com/SeleniumHQ/selenium/releases)
- [Changelog](https://github.com/SeleniumHQ/selenium/blob/trunk/rb/CHANGES)
- [Commits](SeleniumHQ/selenium@selenium-4.40.0...selenium-4.41.0)

---
updated-dependencies:
- dependency-name: selenium-webdriver
  dependency-version: 4.41.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Bumps [rspec-rails](https://github.com/rspec/rspec-rails) from 8.0.2 to 8.0.3.
- [Changelog](https://github.com/rspec/rspec-rails/blob/main/Changelog.md)
- [Commits](rspec/rspec-rails@v8.0.2...v8.0.3)

---
updated-dependencies:
- dependency-name: rspec-rails
  dependency-version: 8.0.3
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Bumps [json](https://github.com/ruby/json) from 2.18.0 to 2.18.1.
- [Release notes](https://github.com/ruby/json/releases)
- [Changelog](https://github.com/ruby/json/blob/master/CHANGES.md)
- [Commits](ruby/json@v2.18.0...v2.18.1)

---
updated-dependencies:
- dependency-name: json
  dependency-version: 2.18.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Bumps [sentry-rails](https://github.com/getsentry/sentry-ruby) from 6.3.0 to 6.3.1.
- [Release notes](https://github.com/getsentry/sentry-ruby/releases)
- [Changelog](https://github.com/getsentry/sentry-ruby/blob/master/CHANGELOG.md)
- [Commits](getsentry/sentry-ruby@6.3.0...6.3.1)

---
updated-dependencies:
- dependency-name: sentry-rails
  dependency-version: 6.3.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Bumps the npm_and_yarn group with 1 update in the / directory: [fast-xml-parser](https://github.com/NaturalIntelligence/fast-xml-parser).


Updates `fast-xml-parser` from 4.5.3 to 5.3.6
- [Release notes](https://github.com/NaturalIntelligence/fast-xml-parser/releases)
- [Changelog](https://github.com/NaturalIntelligence/fast-xml-parser/blob/master/CHANGELOG.md)
- [Commits](https://github.com/NaturalIntelligence/fast-xml-parser/commits/v5.3.6)

---
updated-dependencies:
- dependency-name: fast-xml-parser
  dependency-version: 5.3.6
  dependency-type: indirect
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
Add safe navigation operator before `.map` in `get_existing_student_override`
so that overrides with nil `student_ids` are skipped instead of crashing.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Removed `published: false`
Bumps [sentry-ruby](https://github.com/getsentry/sentry-ruby) from 6.3.1 to 6.4.0.
- [Release notes](https://github.com/getsentry/sentry-ruby/releases)
- [Changelog](https://github.com/getsentry/sentry-ruby/blob/master/CHANGELOG.md)
- [Commits](getsentry/sentry-ruby@6.3.1...6.4.0)

---
updated-dependencies:
- dependency-name: sentry-ruby
  dependency-version: 6.4.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Bumps [sentry-rails](https://github.com/getsentry/sentry-ruby) from 6.3.1 to 6.4.0.
- [Release notes](https://github.com/getsentry/sentry-ruby/releases)
- [Changelog](https://github.com/getsentry/sentry-ruby/blob/master/CHANGELOG.md)
- [Commits](getsentry/sentry-ruby@6.3.1...6.4.0)

---
updated-dependencies:
- dependency-name: sentry-rails
  dependency-version: 6.4.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
@ba88im ba88im requested a review from cycomachead April 24, 2026 18:36
ba88im and others added 6 commits April 26, 2026 23:30
Adds two buttons to the instructor course view that let instructors
enable or disable every assignment for a course in one click. When
enabling, assignments without a due date are skipped to satisfy the
existing enabled_requires_date_present validation, and the count of
skipped assignments is reported back in the flash.

- New PATCH /assignments/bulk_update_enabled collection route
- New AssignmentsController#bulk_update_enabled action (instructor-only)
- New Stimulus actions enableAll / disableAll on assignment_controller
- New buttons rendered next to Sync Assignments on instructor_show
- RSpec coverage for instructor success, role enforcement, due-date
  skipping, and missing-course handling
- Cucumber scenario asserting the buttons render for instructors

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
# Conflicts:
#	app/javascript/controllers/assignment_controller.js
#	app/views/courses/instructor_show.html.erb
#	db/schema.rb
#	features/assignments.feature
…and batch backend

# Conflicts:
#	Gemfile.lock
#	app/controllers/courses_controller.rb
#	app/controllers/user_to_courses_controller.rb
#	app/javascript/controllers/enrollments_controller.js
#	app/views/requests/instructor_index.html.erb
#	db/schema.rb
#	features/enrollments.feature
#	info.yml
#	package-lock.json
#	spec/models/request_spec.rb
This reverts commit 1e4fce3.
@cycomachead
Copy link
Copy Markdown
Member

@ba88im Thanks for working on this. Is there a resolution to how read only tokens are handled when we need to get a token for the google sheets integrations?

@ba88im
Copy link
Copy Markdown
Author

ba88im commented Apr 30, 2026

Yes, for this PR the Google Sheets flow remains backed by the existing course readonly_api_token. The migration stores a digest of that token in api_tokens, and the export endpoint accepts the existing readonly_api_token query param but validates it through the new APIToken lookup. The instructor Requests page still reads @course.readonly_api_token to generate the Google Sheets IMPORTDATA URL, so existing/current Sheets setup keeps working.

The digest-only issue is still relevant for future newly-created tokens: once we add token creation UI, the raw token/Sheets URL will need to be shown once at creation time, since it cannot be reconstructed from token_digest. So I think this PR handles the migration/backward-compatible flow, and Phase 2 should handle one-time display/copy for new read-only Sheets tokens.

@ba88im ba88im reopened this Apr 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants