Masks::Client
The protocol half of the gem: plain Ruby, no Rails, no database. Every entry point here is a class method that builds one of the objects below.
Which one you want depends on what the application is doing:
-
issuer — an app signing people in — discovery, PKCE, the code exchange
-
verifier — an API checking a bearer it was handed
-
resource — an API publishing what it is and which scopes it takes
-
handshake — an app registering itself, once, without a copied secret
Everything reachable from here talks HTTP to a masks issuer and holds no state of its own beyond the discovery cache in ::registry.
Generated from client/lib by bundle exec rake reference. Its shape is the code’s; the
prose is the RDoc in the source.
Masks::Client::Challenge
Section titled “Masks::Client::Challenge”Inherits Masks::Client::Error.
| Attribute | Access |
|---|---|
code |
R |
description |
R |
scope |
R |
status |
R |
::new(code, description, status:, scope: nil, dpop: false)#dpop?
Section titled “#dpop?”#dpop?()Masks::Client::Claims
Section titled “Masks::Client::Claims”| Constant | Value |
|---|---|
AVATARS |
"masks:avatars".freeze |
ORGANIZATION |
"org".freeze |
ORGANIZATIONS |
"orgs".freeze |
| Attribute | Access |
|---|---|
to_h |
R |
::new(claims)#[]
#[](name)#act()#audience
Section titled “#audience”#audience()#avatars
Section titled “#avatars”#avatars()#client_id
Section titled “#client_id”#client_id()#expired?
Section titled “#expired?”#expired?(leeway: 0)#expires_at
Section titled “#expires_at”#expires_at()#issued_at
Section titled “#issued_at”#issued_at()#issuer
Section titled “#issuer”#issuer()#jti()#member!
Section titled “#member!”#member!(*roles, organization: nil)#organization
Section titled “#organization”#organization()#organizations
Section titled “#organizations”#organizations()#permit!
Section titled “#permit!”#permit!(scope)#permits?
Section titled “#permits?”#permits?(scope)#picture
Section titled “#picture”#picture()#scopes
Section titled “#scopes”#scopes()#subject
Section titled “#subject”#subject()#tenant
Section titled “#tenant”#tenant()Masks::Client::Claims::Avatars
Section titled “Masks::Client::Claims::Avatars”| Constant | Value |
|---|---|
STYLES |
%w[photo identicon initials].freeze |
FALLBACK |
"identicon".freeze |
| Attribute | Access |
|---|---|
to_h |
R |
::new(hash)#==(other)#[](style)#photo?
Section titled “#photo?”#photo?()#present?
Section titled “#present?”#present?()Masks::Client::Claims::Organization
Section titled “Masks::Client::Claims::Organization”| Constant | Value |
|---|---|
OWNER |
"owner".freeze |
| Attribute | Access |
|---|---|
to_h |
R |
::new(hash)#==(other)#id()#key()#name()#owner?
Section titled “#owner?”#owner?()#present?
Section titled “#present?”#present?()#role()#role?
Section titled “#role?”#role?(*roles)Masks::Client::Claims::Tenant
Section titled “Masks::Client::Claims::Tenant”| Attribute | Access |
|---|---|
to_h |
R |
::new(hash)#==(other)#name()#present?
Section titled “#present?”#present?()#subdomain
Section titled “#subdomain”#subdomain()#uuid()Masks::Client::Delegations
Section titled “Masks::Client::Delegations”| Constant | Value |
|---|---|
SCOPE |
"masks:delegate:".freeze |
UPSTREAM_ACCESS_TOKEN |
"urn:masks:params:oauth:token-type:upstream_access_token".freeze |
REFUSALS |
%w[invalid_grant insufficient_scope invalid_target unauthorized_client access_denied login_required interaction_required consent_required invalid_scope].freeze |
Held |
Struct.new(:connection, :provider, :provider_name, :label, :subject, :secret, keyword_init: true) |
Upstream |
Struct.new(:access_token, :expires_at, :scope, :secret, keyword_init: true) do def expired?(leeway: 60) Time.now.to_i + leeway >= expires_at.to_i end end |
| Attribute | Access |
|---|---|
client_id |
R |
client_secret |
R |
issuer |
R |
redirect_uri |
R |
::new(issuer:, client_id:, client_secret:, redirect_uri:)#finish
Section titled “#finish”#finish(params:, started:)#start
Section titled “#start”#start(provider:, prompt: nil, max_age: nil, state: SecureRandom.urlsafe_base64(24))#token
Section titled “#token”#token(secret, connection:)Masks::Client::Delegations::Fake
Section titled “Masks::Client::Delegations::Fake”| Constant | Value |
|---|---|
Connected |
Struct.new(:connection, :provider, :subject, :secret, :refused, :unavailable, keyword_init: true) |
| Attribute | Access |
|---|---|
redirect_uri |
R |
releases |
R |
::new(redirect_uri: "https://app.test/connect/callback", lifetime: 3600)#approve
Section titled “#approve”#approve(started, subject: "fake-subject", connection: SecureRandom.uuid)#deny(started, error: "access_denied", description: "the person declined")#finish
Section titled “#finish”#finish(params:, started:)#revoke
Section titled “#revoke”#revoke(connection, reason: "the person stopped this application using that account")#start
Section titled “#start”#start(provider:, prompt: nil, max_age: nil, state: SecureRandom.urlsafe_base64(24))#token
Section titled “#token”#token(secret, connection:)#unavailable
Section titled “#unavailable”#unavailable(connection, now: true)Masks::Client::Delegations::Refused
Section titled “Masks::Client::Delegations::Refused”Inherits Error.
| Attribute | Access |
|---|---|
code |
R |
description |
R |
secret |
R |
::new(code, description, secret: nil)#signed_in_again?
Section titled “#signed_in_again?”#signed_in_again?()Masks::Client::Delegations::Unavailable
Section titled “Masks::Client::Delegations::Unavailable”Inherits Error.
| Attribute | Access |
|---|---|
secret |
R |
::new(message, secret: nil)Masks::Client::Error
Section titled “Masks::Client::Error”Inherits StandardError.
Masks::Client::Forbidden
Section titled “Masks::Client::Forbidden”Inherits Masks::Client::Challenge.
::new(code, description, scope: nil)Masks::Client::HTTP
Section titled “Masks::Client::HTTP”| Constant | Value |
|---|---|
OPEN_TIMEOUT |
5 |
READ_TIMEOUT |
10 |
#default_headers
Section titled “#default_headers”#default_headers()#delete
Section titled “#delete”#delete(url, headers = {})#fetch
Section titled “#fetch”#fetch(url, headers = {})#get(url, headers = {})#json(verb, url, body, headers)#parse
Section titled “#parse”#parse(response)#post_form
Section titled “#post_form”#post_form(url, form, headers = {})#post_json
Section titled “#post_json”#post_json(url, body, headers = {})#put_json
Section titled “#put_json”#put_json(url, body, headers = {})#request
Section titled “#request”#request(request)Masks::Client::Handshake
Section titled “Masks::Client::Handshake”| Constant | Value |
|---|---|
PATH |
"/handshake".freeze |
GRANT_TYPES |
%w[authorization_code refresh_token].freeze |
AUTH_METHOD |
"client_secret_basic".freeze |
| Attribute | Access |
|---|---|
backchannel_logout_uri |
R |
issuer |
R |
name |
R |
redirect_uris |
R |
resource |
R |
return_to |
R |
scope |
R |
::new(issuer, name:, resource:, redirect_uris:, return_to:, scope: Session::DEFAULT_SCOPE, backchannel_logout_uri: nil)#complete
Section titled “#complete”#complete(params, state:)#endpoint
Section titled “#endpoint”#endpoint()#redeem
Section titled “#redeem”#redeem(token)#start
Section titled “#start”#start(state: SecureRandom.urlsafe_base64(32))#url(state:)Masks::Client::Introspection
Section titled “Masks::Client::Introspection”Inherits Masks::Client::Claims.
#active?
Section titled “#active?”#active?()#member!
Section titled “#member!”#member!(*roles, organization: nil)#nickname
Section titled “#nickname”#nickname()#permit!
Section titled “#permit!”#permit!(scope)#permits?
Section titled “#permits?”#permits?(scope)#token_type
Section titled “#token_type”#token_type()Masks::Client::InvalidToken
Section titled “Masks::Client::InvalidToken”Inherits Masks::Client::Error.
Masks::Client::Issuer
Section titled “Masks::Client::Issuer”| Constant | Value |
|---|---|
DISCOVERY_PATH |
"/.well-known/openid-configuration".freeze |
TTL |
300 |
| Attribute | Access |
|---|---|
url |
R |
::new(url, ttl: TTL)::normalize
Section titled “::normalize”::normalize(url)::resolve
Section titled “::resolve”::resolve(issuer, ttl: TTL)#avatar_styles
Section titled “#avatar_styles”#avatar_styles()#avatar_url
Section titled “#avatar_url”#avatar_url(subject, style: nil, size: nil)#backchannel_logout?
Section titled “#backchannel_logout?”#backchannel_logout?()#discovery
Section titled “#discovery”#discovery()#endpoint
Section titled “#endpoint”#endpoint(name)#jwks()#refresh!
Section titled “#refresh!”#refresh!()#tenant
Section titled “#tenant”#tenant()Masks::Client::Logout
Section titled “Masks::Client::Logout”| Constant | Value |
|---|---|
EVENT |
"http://schemas.openid.net/event/backchannel-logout".freeze |
ALGORITHMS |
Verifier::ALGORITHMS |
LEEWAY |
60 |
| Attribute | Access |
|---|---|
claims |
R |
::new(claims)::verify
Section titled “::verify”::verify(token, issuer:, audience:, algorithms: ALGORITHMS)#issued_at
Section titled “#issued_at”#issued_at()#jti()#sid()#subject
Section titled “#subject”#subject()#validate!
Section titled “#validate!”#validate!()Masks::Client::Pkce
Section titled “Masks::Client::Pkce”| Constant | Value |
|---|---|
METHOD |
"S256".freeze |
| Attribute | Access |
|---|---|
verifier |
R |
::generate
Section titled “::generate”::generate()::new(verifier)#challenge
Section titled “#challenge”#challenge()#method
Section titled “#method”#method()Masks::Client::Proof
Section titled “Masks::Client::Proof”| Constant | Value |
|---|---|
SCHEME |
"DPoP".freeze |
TYPE |
"dpop+jwt".freeze |
ALGORITHMS |
%w[ES256 ES384 ES512 PS256 PS384 PS512 RS256].freeze |
SECRET |
%w[d p q dp dq qi k].freeze |
THUMBED |
{ "EC" => %w[crv kty x y], "RSA" => %w[e kty n] }.freeze |
LEEWAY |
30 |
WINDOW |
60 |
MEMORY |
WINDOW + (LEEWAY * 2) |
MEMORY_LOCK |
Mutex.new |
::digest
Section titled “::digest”::digest(value)::memory
Section titled “::memory”::memory()::new(proof, method:, url:, replay: nil)::thumbprint
Section titled “::thumbprint”::thumbprint(jwk)#check!
Section titled “#check!”#check!(access_token:, jkt:)Masks::Client::Proof::Invalid
Section titled “Masks::Client::Proof::Invalid”Inherits Masks::Client::Error.
Masks::Client::Proof::Memory
Section titled “Masks::Client::Proof::Memory”::new()#first?
Section titled “#first?”#first?(key, expires_in:)Masks::Client::Rack
Section titled “Masks::Client::Rack”| Constant | Value |
|---|---|
CLAIMS |
"masks.claims".freeze |
ERROR |
"masks.error".freeze |
::new(app, resource:, scope: nil, only: nil, optional: false)#call(env)Masks::Client::Registration
Section titled “Masks::Client::Registration”| Constant | Value |
|---|---|
REFUSED |
[ 401, 403 ].freeze |
GONE |
(REFUSED + [ 404 ]).freeze |
| Attribute | Access |
|---|---|
access_token |
R |
issuer |
R |
metadata |
R |
::create
Section titled “::create”::create(issuer, token: nil, **attributes)::fallback
Section titled “::fallback”::fallback(value, default)::held
Section titled “::held”::held(issuer, credentials)::new(issuer, body)::stringify
Section titled “::stringify”::stringify(attributes)#authorization
Section titled “#authorization”#authorization()#client_id
Section titled “#client_id”#client_id()#client_secret
Section titled “#client_secret”#client_secret()#delete
Section titled “#delete”#delete()#known?
Section titled “#known?”#known?()#read()#session
Section titled “#session”#session(redirect_uri:, scope: Session::DEFAULT_SCOPE)#update
Section titled “#update”#update(**attributes)#uri()Masks::Client::Registry
Section titled “Masks::Client::Registry”::new()#[](url, ttl: Issuer::TTL)#clear!
Section titled “#clear!”#clear!()Masks::Client::Rejected
Section titled “Masks::Client::Rejected”Inherits Masks::Client::Error.
| Attribute | Access |
|---|---|
code |
R |
description |
R |
status |
R |
::new(code, description, status: nil)Masks::Client::Resource
Section titled “Masks::Client::Resource”| Constant | Value |
|---|---|
PRESENTED |
`/\A(Bearer |
METADATA_PATH |
"/.well-known/oauth-protected-resource".freeze |
REQUIRED |
%w[iss sub exp].freeze |
| Attribute | Access |
|---|---|
issuer |
R |
scopes |
R |
url |
R |
::new(issuer:, url:, scopes: [], metadata_url: nil, algorithms: Verifier::ALGORITHMS, required: REQUIRED, verifier: nil, replay: Proof.memory)#authenticate
Section titled “#authenticate”#authenticate(authorization, scope: nil, role: nil, organization: nil, proof: nil, method: nil, url: nil)#challenge
Section titled “#challenge”#challenge(error = nil)#metadata
Section titled “#metadata”#metadata()#metadata_url
Section titled “#metadata_url”#metadata_url()#token
Section titled “#token”#token(authorization)Masks::Client::Session
Section titled “Masks::Client::Session”| Constant | Value |
|---|---|
DEFAULT_SCOPE |
%w[openid profile email].freeze |
ORGANIZATION |
"organization".freeze |
ORGANIZATION_KEY |
/\A[a-z0-9][a-z0-9-]*\z/ |
ASSERTION_TYPE |
"urn:ietf:params:oauth:client-assertion-type:jwt-bearer".freeze |
ASSERTION_LIFETIME |
60 |
| Attribute | Access |
|---|---|
client_id |
R |
client_secret |
R |
issuer |
R |
key_id |
R |
private_key |
R |
redirect_uri |
R |
scope |
R |
::new(issuer:, client_id:, redirect_uri: nil, client_secret: nil, private_key: nil, key_id: nil, scope: DEFAULT_SCOPE)::organization_key
Section titled “::organization_key”::organization_key(value)#client_credentials
Section titled “#client_credentials”#client_credentials(scope: nil, resource: nil)#complete
Section titled “#complete”#complete(code:, verifier:, resource: nil)#end_session_url
Section titled “#end_session_url”#end_session_url(post_logout_redirect_uri: nil, state: nil, id_token_hint: nil)#exchange
Section titled “#exchange”#exchange(subject_token, scope: nil, resource: nil, lifetime: nil, requested_token_type: nil, audience: nil, subject_token_type: Tokens::ACCESS_TOKEN, actor_token: nil, actor_token_type: Tokens::ACCESS_TOKEN)#identity
Section titled “#identity”#identity(tokens)#introspect
Section titled “#introspect”#introspect(token, hint: nil)#logout_token
Section titled “#logout_token”#logout_token(token)#profile
Section titled “#profile”#profile(tokens)#refresh
Section titled “#refresh”#refresh(refresh_token, resource: nil, scope: nil)#revoke
Section titled “#revoke”#revoke(token, hint: nil)#start
Section titled “#start”#start(resource: nil, prompt: nil, scope: nil, state: SecureRandom.urlsafe_base64(24), nonce: SecureRandom.urlsafe_base64(24), max_age: nil, organization: nil)#userinfo
Section titled “#userinfo”#userinfo(access_token)Masks::Client::Stores
Section titled “Masks::Client::Stores”Masks::Client::Stores::Memory
Section titled “Masks::Client::Stores::Memory”| Attribute | Access |
|---|---|
held |
R |
::new(held = {})#read()#write
Section titled “#write”#write(value)Masks::Client::Stores::Session
Section titled “Masks::Client::Stores::Session”::new(session, key)#read()#write
Section titled “#write”#write(value)Masks::Client::Tokens
Section titled “Masks::Client::Tokens”| Constant | Value |
|---|---|
EXCHANGE |
"urn:ietf:params:oauth:grant-type:token-exchange".freeze |
ACCESS_TOKEN |
"urn:ietf:params:oauth:token-type:access_token".freeze |
ID_TOKEN |
"urn:ietf:params:oauth:token-type:id_token".freeze |
| Attribute | Access |
|---|---|
access_token |
R |
delegations |
R |
expires_in |
R |
id_token |
R |
obtained_at |
R |
refresh_token |
R |
scope |
R |
token_type |
R |
::from_h
Section titled “::from_h”::from_h(data)::granted
Section titled “::granted”::granted(body)::new(body)#authorization
Section titled “#authorization”#authorization()#expired?
Section titled “#expired?”#expired?(leeway: 30)#expires_at
Section titled “#expires_at”#expires_at()#scopes
Section titled “#scopes”#scopes()#to_h()Masks::Client::Tracker
Section titled “Masks::Client::Tracker”| Constant | Value |
|---|---|
TTL |
3600 |
LIMIT |
5 |
| Attribute | Access |
|---|---|
limit |
R |
store |
R |
ttl |
R |
::new(store, ttl: TTL, limit: LIMIT)#amend
Section titled “#amend”#amend(id, **data)#claim
Section titled “#claim”#claim(id)#clear!
Section titled “#clear!”#clear!()#open(**data)#size()Masks::Client::Tracker::Entry
Section titled “Masks::Client::Tracker::Entry”| Attribute | Access |
|---|---|
data |
R |
expires_at |
R |
id |
R |
opened_at |
R |
::from_h
Section titled “::from_h”::from_h(id, held)::new(id:, data:, opened_at:, expires_at:)#[](key)#live?
Section titled “#live?”#live?(now = Time.now.to_f)#to_h()Masks::Client::Unauthenticated
Section titled “Masks::Client::Unauthenticated”Inherits Masks::Client::Challenge.
::new(description = "a bearer token is required", code: nil)Masks::Client::Unauthorized
Section titled “Masks::Client::Unauthorized”Inherits Masks::Client::Challenge.
::new(description, code: "invalid_token", dpop: false)Masks::Client::Unreachable
Section titled “Masks::Client::Unreachable”Inherits Masks::Client::Error.
Masks::Client::Unregistered
Section titled “Masks::Client::Unregistered”Inherits Masks::Client::Rejected.
| Constant | Value |
|---|---|
CODE |
"invalid_client".freeze |
::raised_by?
Section titled “::raised_by?”::raised_by?(body)Masks::Client::Verifier
Section titled “Masks::Client::Verifier”| Constant | Value |
|---|---|
ALGORITHMS |
%w[RS256 ES256].freeze |
ACCESS_TOKEN |
"at+jwt".freeze |
LOGOUT_TOKEN |
"logout+jwt".freeze |
ID_TOKEN |
[ "jwt", nil ].freeze |
| Attribute | Access |
|---|---|
audience |
R |
issuer |
R |
::new(issuer, audience:, algorithms: ALGORITHMS)#verify
Section titled “#verify”#verify(token, required: %w[iss sub exp], typ: ID_TOKEN)