Previously, API authentication used Rodauth's login and jwt features, which use a session-based approach, storing session information in a JWT. This expands API authentication to also support personal access tokens. There can be multiple personal access tokens per account. Personal access tokens are stored in the ApiKey model, using an owner_table of accounts. This does not conflict with existing ApiKey usage. I thought of using a separate database table and model for this, but it would have been very similar to ApiKey, and it seems simpler to expand ApiKey for this purpose than to introduce a distinct but very similar concept. I implemented this as a custom Rodauth feature, but the Rodauth feature is only partially generic currently. Potentially, it could be made fully generic and moved into Rodauth in the future. The feature is stored in rodauth/features/personal_access_token.rb, because storing it under lib caused issues with loader.rb's eager loading. Personal access tokens do not use sessions, though it does have rodauth.session return an appropriate hash. As active_sessions requires a session-based approach, this does not call rodauth.check_active_session if a personal access token is used. This is not a loss of security, because you can revoke a personal access token just like you can revoke an active session. This does not use custom authorization for personal access tokens. That will come later.
39 lines
1.0 KiB
Ruby
39 lines
1.0 KiB
Ruby
# frozen-string-literal: true
|
|
|
|
module Rodauth
|
|
Feature.define(:personal_access_token, :PersonalAccessToken) do
|
|
depends :jwt
|
|
|
|
auth_value_method :pat_authorization_remove, /\ABearer:?\s+pat-/
|
|
session_key :session_pat_key, :pat_id
|
|
|
|
def use_pat?
|
|
pat_authorization_remove.match?(request.env["HTTP_AUTHORIZATION"].to_s)
|
|
end
|
|
|
|
# We override use_jwt? in the rodauth configuration, so this is not used.
|
|
# def use_jwt?
|
|
# super && !use_pat?
|
|
# end
|
|
|
|
def session
|
|
return @session if defined?(@session)
|
|
return super unless use_pat?
|
|
|
|
@session = s = {}
|
|
|
|
token = request.env["HTTP_AUTHORIZATION"].sub(pat_authorization_remove, "")
|
|
token_id, key = token.split("-", 2)
|
|
|
|
return s unless (uuid = UBID.to_uuid(token_id))
|
|
return s unless (api_key = ApiKey[owner_table: "accounts", id: uuid, is_valid: true])
|
|
return s unless timing_safe_eql?(api_key.key, key)
|
|
|
|
set_session_value(session_key, api_key.owner_id)
|
|
set_session_value(session_pat_key, uuid)
|
|
|
|
s
|
|
end
|
|
end
|
|
end
|