Conversation
Adds OAuth-style authorization scopes carried inside the encrypted auth token (AuthScope, "resource:permission" syntax with "*" wildcard). Every policy checks scope before role/ownership logic; the web-app session token is FULL (*:write) while the account-detail endpoint mints a READ_ONLY (*:read) key via AuthorizedAccount / AuthorizeAccount. Mirrors Credence f51f5e6. Tyto extension (no Credence counterpart): geo-validated attendance. - migration 009 adds encrypted longitude_secure/latitude_secure on attendances - GeoFence (app/lib/geo_fence.rb): a pure, domain-agnostic Haversine geofence primitive (contains?/distance_to) - RecordAttendance owns the attendance-domain rules: radius from ATTENDANCE_RADIUS_M (default 55 m), the "no location -> no geofence" rule, and coordinate validation; stores submitted coords encrypted at rest - staff PUT toggle for event attendances - OutOfRangeError -> 422, InvalidCoordinatesError -> 400 Suite: 226 runs, 0 failures. RuboCop (93 files) + bundler-audit clean.
Adds Google Single Sign-On via OpenID Connect. The App exchanges the OAuth code for a Google-signed id_token and hands it to POST /api/v1/auth/sso. The API verifies that id_token against Google's JWKS (RS256) and checks iss/aud/exp, then reads identity claims directly from the verified token -- no userinfo callback. Identity is keyed on (provider, external_id) via a new sso_identities table, not email: a known identity returns its account, a verified-email match links to an existing account, an unverified-email collision is refused (409), otherwise a new member account is created. SSO mints a FULL-scope auth token. The account envelope now serializes `avatar` so the App can show the Google photo. Re-architected from the reference branch's GitHub access-token + userinfo flow to OIDC id_token verification (jwt gem, OpenSSL/RS256 -- RbNaCl cannot do RSA). The verifier is a generic OidcVerifier(jwks_uri, audience, allowed_issuers) caching JWKS keys by kid; GoogleIdToken is a thin configured instance. Suite: 245 runs / 537 assertions / 0 failures. RuboCop (102) + bundler-audit clean.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Week-14 API authorization work. This is the lead branch of a two-branch
chain (
8-auth-scope→8-sso), opened againstmain, so the PR carriesboth branches' work cumulatively:
8-auth-scope— OAuth-style authorization scopes carried inside theencrypted auth token (
AuthScope,resource:permission+*wildcard);every policy checks scope before role/ownership. Plus the Tyto-only
geo-validated attendance feature: Haversine proximity to the event
location with student coordinates encrypted at rest.
8-sso— "Sign in with Google" via OpenID Connect: the API verifiesa Google-signed
id_tokenagainst Google's JWKS (RS256,iss/aud/exp),reads identity claims, and finds/links/creates an account keyed on
(provider, external_id).Mirror branches of Credence
f51f5e6+98b4856, re-architected for Google OIDC.Plan
AuthScope (
8-auth-scope)AuthScopelib (FULL/READ_ONLY, resource-agnostic) + token scope plumbingAuthorizedAccount+AuthorizeAccount; account-detail mints aREAD_ONLYtokenGeoFenceprimitive,RecordAttendancerules, staff toggle route{id, username}identity (folded into the payload)Google SSO (
8-sso)OidcVerifier+GoogleIdToken(jwt gem, RS256/JWKS, claim checks)GoogleAccountmapper;sso_identitiesmigration +SsoIdentitymodelFindOrCreateSsoAccount(find → verified-email link → create)AuthenticateSso+POST /api/v1/auth/sso;avatarserialized in account envelopeDeferred (per project workflow)
mainChanges
Lib —
auth_scope.rb,auth_token.rb(scope + minimal payload),geo_fence.rb(pure Haversine),oidc_verifier.rb(generic JWKS/RS256 verifier),google_id_token.rb(Google-configured instance).Models —
authorized_account.rb,google_account.rb,sso_identity.rb;account.rb(avatarinto_json),attendance.rb(encrypted coords).Services —
authorize_account.rb,authenticate_sso.rb,find_or_create_sso_account.rb; scope threaded throughauthenticate_account+ thecreate_*/enroll_*services;record_attendance.rb(coords + geofence +OutOfRangeError/InvalidCoordinatesError).Policies — scope guards added to course/event/location/attendance/enrollment policies.
Migrations —
009encrypted attendance coords;010sso_identities(unique(provider, external_id)).Config —
GOOGLE_CLIENT_ID,ATTENDANCE_RADIUS_Minsecrets-example.yml.Design notes
id_tokenagainst Google's JWKS — zero network hops, audience-bound (aud == GOOGLE_CLIENT_ID), replay-resistant. Re-architecture vs Credence's GitHub access-token + userinfo flow.(provider, external_id = sub), not email.email_verifiedonly gates the optional verified-email link to a pre-existing account; an unverified-email collision with an existing account neither links nor duplicates → newEmailConflictError→ 409.{id, username}; the API re-derives roles/policies from the id each request. The rich envelope rides in the plaintext response only.geo_fence.rb; attendance-domain rules (radius, no-location, coord validation) live inRecordAttendance; the temporallive_now?check stays inAttendancePolicy.Test plan
stateguard, password login unaffected)