mirror of https://github.com/ory/hydra
569 lines
22 KiB
Go
569 lines
22 KiB
Go
// Copyright © 2022 Ory Corp
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
package flow
|
|
|
|
import (
|
|
"context"
|
|
"time"
|
|
|
|
"github.com/gofrs/uuid"
|
|
"github.com/pkg/errors"
|
|
|
|
"github.com/ory/hydra/v2/aead"
|
|
"github.com/ory/hydra/v2/client"
|
|
"github.com/ory/pop/v6"
|
|
"github.com/ory/x/pointerx"
|
|
"github.com/ory/x/sqlcon"
|
|
"github.com/ory/x/sqlxx"
|
|
)
|
|
|
|
// FlowState* constants enumerate the states of a flow. The below graph
|
|
// describes possible flow state transitions.
|
|
//
|
|
// stateDiagram-v2
|
|
// [*] --> DEVICE_UNUSED: GET /oauth2/device/verify
|
|
// DEVICE_UNUSED --> DEVICE_USED: submit user code
|
|
// DEVICE_USED --> LOGIN_UNUSED: to verifier
|
|
// [*] --> LOGIN_UNUSED: GET /oauth2/auth
|
|
// LOGIN_UNUSED --> LOGIN_UNUSED: accept login
|
|
// LOGIN_UNUSED --> LOGIN_USED: submit login verifier
|
|
// LOGIN_UNUSED --> LOGIN_ERROR: reject login
|
|
// LOGIN_ERROR --> [*]
|
|
// LOGIN_USED --> CONSENT_UNUSED
|
|
// CONSENT_UNUSED --> CONSENT_UNUSED: accept consent
|
|
// CONSENT_UNUSED --> CONSENT_USED: submit consent verifier
|
|
// CONSENT_UNUSED --> CONSENT_ERROR: reject consent
|
|
// CONSENT_ERROR --> [*]
|
|
// CONSENT_USED --> [*]
|
|
|
|
type State int16
|
|
|
|
const (
|
|
// FlowStateLoginInitialized is not used anymore, but is kept for
|
|
// backwards compatibility. New flows start at FlowStateLoginUnused.
|
|
FlowStateLoginInitialized = State(1)
|
|
|
|
// FlowStateLoginUnused indicates that the login has been authenticated, but
|
|
// the User Agent hasn't picked up the result yet.
|
|
FlowStateLoginUnused = State(2)
|
|
|
|
// FlowStateLoginUsed indicates that the User Agent is requesting consent and
|
|
// Hydra has invalidated the login request. This is a short-lived state
|
|
// because the transition to FlowStateConsentInitialized should happen while
|
|
// handling the request that triggered the transition to FlowStateLoginUsed.
|
|
FlowStateLoginUsed = State(3)
|
|
|
|
// FlowStateConsentInitialized is not used anymore, but is kept for
|
|
// backwards compatibility. New flows start at FlowStateConsentUnused.
|
|
FlowStateConsentInitialized = State(4)
|
|
|
|
FlowStateConsentUnused = State(5)
|
|
FlowStateConsentUsed = State(6)
|
|
|
|
// DeviceFlowStateInitialized is not used anymore, but is kept for
|
|
// backwards compatibility. New flows start at DeviceFlowStateUnused.
|
|
DeviceFlowStateInitialized = State(7)
|
|
|
|
// DeviceFlowStateUnused indicates that the login has been authenticated, but
|
|
// the User Agent hasn't picked up the result yet.
|
|
DeviceFlowStateUnused = State(8)
|
|
|
|
// DeviceFlowStateUsed indicates that the User Agent is requesting consent and
|
|
// Hydra has invalidated the login request. This is a short-lived state
|
|
// because the transition to DeviceFlowStateConsentInitialized should happen while
|
|
// handling the request that triggered the transition to DeviceFlowStateUsed.
|
|
DeviceFlowStateUsed = State(9)
|
|
|
|
// TODO: Refactor error handling to persist error codes instead of JSON
|
|
// strings. Currently we persist errors as JSON strings in the LoginError
|
|
// and ConsentError fields. This shouldn't be necessary because the different
|
|
// errors are enumerable; most of them have error codes defined in Fosite. It
|
|
// is possible to define a mapping between error codes and the metadata that
|
|
// is currently persisted with each erred Flow. This mapping would be used in
|
|
// GetConsentRequest, HandleConsentRequest, GetHandledLoginRequest, etc. An
|
|
// ErrorContext field can be introduced later if it becomes necessary.
|
|
// If the above is implemented, merge the LoginError and ConsentError fields
|
|
// and use the following FlowStates when converting to/from
|
|
// [Handled]{Login|Consent}Request:
|
|
FlowStateLoginError = State(128)
|
|
FlowStateConsentError = State(129)
|
|
)
|
|
|
|
func (s State) ConsentWasUsed() bool { return s == FlowStateConsentUsed || s == FlowStateConsentError }
|
|
func (s State) LoginWasUsed() bool { return s == FlowStateLoginUsed || s == FlowStateLoginError }
|
|
|
|
func (s State) IsAny(expected ...State) error {
|
|
for _, e := range expected {
|
|
if s == e {
|
|
return nil
|
|
}
|
|
}
|
|
return errors.Errorf("invalid flow state: expected one of %v, got %d", expected, s)
|
|
}
|
|
|
|
// Flow is an abstraction used in the persistence layer to unify LoginRequest,
|
|
// HandledLoginRequest, ConsentRequest, and AcceptOAuth2ConsentRequest.
|
|
//
|
|
// TODO: Deprecate the structs that are made obsolete by the Flow concept.
|
|
// Context: Before Flow was introduced, the API and the database used the same
|
|
// structs, LoginRequest and HandledLoginRequest. These two tables and structs
|
|
// were merged into a new concept, Flow, in order to optimize the persistence
|
|
// layer. We currently limit the use of Flow to the persistence layer and keep
|
|
// using the original structs in the API in order to minimize the impact of the
|
|
// database refactoring on the API.
|
|
type Flow struct {
|
|
// ID is the identifier of the login request.
|
|
//
|
|
// The struct field is named ID for compatibility with gobuffalo/pop, and is
|
|
// the primary key in the database.
|
|
//
|
|
// The database column should be named `login_challenge_id`, but is not for
|
|
// historical reasons.
|
|
//
|
|
// This is not the same as the login session ID.
|
|
ID string `db:"login_challenge" json:"i"`
|
|
NID uuid.UUID `db:"nid" json:"n"`
|
|
|
|
// RequestedScope contains the OAuth 2.0 Scope requested by the OAuth 2.0 Client.
|
|
//
|
|
// required: true
|
|
RequestedScope sqlxx.StringSliceJSONFormat `db:"requested_scope" json:"rs,omitempty"`
|
|
|
|
// RequestedAudience contains the access token audience as requested by the OAuth 2.0 Client.
|
|
//
|
|
// required: true
|
|
RequestedAudience sqlxx.StringSliceJSONFormat `db:"requested_at_audience" json:"ra,omitempty"`
|
|
|
|
// LoginSkip, if true, implies that the client has requested the same scopes from the same user previously.
|
|
// If true, you can skip asking the user to grant the requested scopes, and simply forward the user to the redirect URL.
|
|
//
|
|
// This feature allows you to update / set session information.
|
|
//
|
|
// required: true
|
|
LoginSkip bool `db:"-" json:"ls,omitempty"`
|
|
|
|
// Subject is the user ID of the end-user that authenticated. Now, that end user needs to grant or deny the scope
|
|
// requested by the OAuth 2.0 client. If this value is set and `skip` is true, you MUST include this subject type
|
|
// when accepting the login request, or the request will fail.
|
|
//
|
|
// required: true
|
|
Subject string `db:"subject" json:"s,omitempty"`
|
|
|
|
// OpenIDConnectContext provides context for the (potential) OpenID Connect context. Implementation of these
|
|
// values in your app are optional but can be useful if you want to be fully compliant with the OpenID Connect spec.
|
|
OpenIDConnectContext *OAuth2ConsentRequestOpenIDConnectContext `db:"oidc_context" json:"oc"`
|
|
|
|
// Client is the OAuth 2.0 Client that initiated the request.
|
|
//
|
|
// required: true
|
|
Client *client.Client `db:"-" json:"c,omitempty"`
|
|
ClientID string `db:"client_id" json:"ci,omitempty"`
|
|
|
|
// RequestURL is the original OAuth 2.0 Authorization URL requested by the OAuth 2.0 client. It is the URL which
|
|
// initiates the OAuth 2.0 Authorization Code or OAuth 2.0 Implicit flow. This URL is typically not needed, but
|
|
// might come in handy if you want to deal with additional request parameters.
|
|
//
|
|
// required: true
|
|
RequestURL string `db:"request_url" json:"r,omitempty"`
|
|
|
|
// SessionID is the login session ID. If the user-agent reuses a login session (via cookie / remember flag)
|
|
// this ID will remain the same. If the user-agent did not have an existing authentication session (e.g. remember is false)
|
|
// this will be a new random value. This value is used as the "sid" parameter in the ID Token and in OIDC Front-/Back-
|
|
// channel logout. Its value can generally be used to associate consecutive login requests by a certain user.
|
|
SessionID sqlxx.NullString `db:"login_session_id" json:"si,omitempty"`
|
|
|
|
// IdentityProviderSessionID is the session ID of the end-user that authenticated.
|
|
// If specified, we will use this value to propagate the logout.
|
|
IdentityProviderSessionID sqlxx.NullString `db:"-" json:"is,omitempty"`
|
|
|
|
LoginCSRF string `db:"-" json:"lc,omitempty"`
|
|
|
|
RequestedAt time.Time `db:"requested_at" json:"ia,omitempty"`
|
|
|
|
State State `db:"-" json:"q,omitempty"`
|
|
|
|
// LoginRemember, if set to true, tells ORY Hydra to remember this user by telling the user agent (browser) to store
|
|
// a cookie with authentication data. If the same user performs another OAuth 2.0 Authorization Request, he/she
|
|
// will not be asked to log in again.
|
|
LoginRemember bool `db:"-" json:"lr,omitempty"`
|
|
|
|
// LoginRememberFor sets how long the authentication should be remembered for in seconds. If set to `0`, the
|
|
// authorization will be remembered for the duration of the browser session (using a session cookie).
|
|
LoginRememberFor int `db:"-" json:"lf,omitempty"`
|
|
|
|
// LoginExtendSessionLifespan, if set to true, session cookie expiry time will be updated when session is
|
|
// refreshed (login skip=true).
|
|
LoginExtendSessionLifespan bool `db:"-" json:"ll,omitempty"`
|
|
|
|
// ACR sets the Authentication AuthorizationContext Class Reference value for this authentication session. You can use it
|
|
// to express that, for example, a user authenticated using two factor authentication.
|
|
ACR string `db:"acr" json:"a,omitempty"`
|
|
|
|
// AMR sets the Authentication Methods References value for this
|
|
// authentication session. You can use it to specify the method a user used to
|
|
// authenticate. For example, if the acr indicates a user used two factor
|
|
// authentication, the amr can express they used a software-secured key.
|
|
AMR sqlxx.StringSliceJSONFormat `db:"amr" json:"am,omitempty"`
|
|
|
|
// ForceSubjectIdentifier forces the "pairwise" user ID of the end-user that authenticated. The "pairwise" user ID refers to the
|
|
// (Pairwise Identifier Algorithm)[http://openid.net/specs/openid-connect-core-1_0.html#PairwiseAlg] of the OpenID
|
|
// Connect specification. It allows you to set an obfuscated subject ("user") identifier that is unique to the client.
|
|
//
|
|
// Please note that this changes the user ID on endpoint /userinfo and sub claim of the ID Token. It does not change the
|
|
// sub claim in the OAuth 2.0 Introspection.
|
|
//
|
|
// Per default, ORY Hydra handles this value with its own algorithm. In case you want to set this yourself
|
|
// you can use this field. Please note that setting this field has no effect if `pairwise` is not configured in
|
|
// ORY Hydra or the OAuth 2.0 Client does not expect a pairwise identifier (set via `subject_type` key in the client's
|
|
// configuration).
|
|
//
|
|
// Please also be aware that ORY Hydra is unable to properly compute this value during authentication. This implies
|
|
// that you have to compute this value on every authentication process (probably depending on the client ID or some
|
|
// other unique value).
|
|
//
|
|
// If you fail to compute the proper value, then authentication processes which have id_token_hint set might fail.
|
|
ForceSubjectIdentifier string `db:"-" json:"fs,omitempty"`
|
|
|
|
// Context is an optional object which can hold arbitrary data. The data will be made available when fetching the
|
|
// consent request under the "context" field. This is useful in scenarios where login and consent endpoints share
|
|
// data.
|
|
Context sqlxx.JSONRawMessage `db:"context" json:"ct"`
|
|
|
|
LoginError *RequestDeniedError `db:"-" json:"le,omitempty"`
|
|
LoginAuthenticatedAt sqlxx.NullTime `db:"-" json:"la,omitempty"`
|
|
|
|
// DeviceChallengeID is the device request's challenge ID
|
|
DeviceChallengeID sqlxx.NullString `db:"device_challenge_id" json:"di,omitempty"`
|
|
// DeviceCodeRequestID is the device request's ID
|
|
DeviceCodeRequestID sqlxx.NullString `db:"device_code_request_id" json:"dr,omitempty"`
|
|
// DeviceCSRF is the device request's CSRF
|
|
DeviceCSRF sqlxx.NullString `db:"-" json:"dc,omitempty"`
|
|
// DeviceHandledAt contains the timestamp the device user_code verification request was handled
|
|
DeviceHandledAt sqlxx.NullTime `db:"-" json:"dh,omitempty"`
|
|
|
|
// ConsentRequestID is the identifier of the consent request.
|
|
// The database column should be named `consent_request_id`, but is not for historical reasons.
|
|
ConsentRequestID sqlxx.NullString `db:"consent_challenge_id" json:"cc,omitempty"`
|
|
// ConsentSkip, if true, implies that the client has requested the same scopes from the same user previously.
|
|
// If true, you must not ask the user to grant the requested scopes. You must however either allow or deny the
|
|
// consent request using the usual API call.
|
|
ConsentSkip bool `db:"consent_skip" json:"cs,omitempty"`
|
|
ConsentCSRF sqlxx.NullString `db:"-" json:"cr,omitempty"`
|
|
|
|
// GrantedScope sets the scope the user authorized the client to use. Should be a subset of `requested_scope`.
|
|
GrantedScope sqlxx.StringSliceJSONFormat `db:"granted_scope" json:"gs,omitempty"`
|
|
|
|
// GrantedAudience sets the audience the user authorized the client to use. Should be a subset of `requested_access_token_audience`.
|
|
GrantedAudience sqlxx.StringSliceJSONFormat `db:"granted_at_audience" json:"ga,omitempty"`
|
|
|
|
// ConsentRemember, if set to true, tells ORY Hydra to remember this consent authorization and reuse it if the same
|
|
// client asks the same user for the same, or a subset of, scope.
|
|
ConsentRemember bool `db:"consent_remember" json:"ce,omitempty"`
|
|
|
|
// ConsentRememberFor sets how long the consent authorization should be remembered for in seconds. If set to `0`, the
|
|
// authorization will be remembered indefinitely.
|
|
ConsentRememberFor *int `db:"consent_remember_for" json:"cf"`
|
|
|
|
// ConsentHandledAt contains the timestamp the consent request was handled.
|
|
ConsentHandledAt sqlxx.NullTime `db:"consent_handled_at" json:"ch,omitempty"`
|
|
|
|
ConsentError *RequestDeniedError `db:"-" json:"cx"`
|
|
SessionIDToken sqlxx.MapStringInterface `db:"session_id_token" faker:"-" json:"st"`
|
|
SessionAccessToken sqlxx.MapStringInterface `db:"session_access_token" faker:"-" json:"sa"`
|
|
}
|
|
|
|
// HandleDeviceUserAuthRequest updates the flows fields from a handled request.
|
|
func (f *Flow) HandleDeviceUserAuthRequest(h *HandledDeviceUserAuthRequest) error {
|
|
if err := f.State.IsAny(DeviceFlowStateInitialized, DeviceFlowStateUnused); err != nil {
|
|
return err
|
|
}
|
|
|
|
f.State = DeviceFlowStateUnused
|
|
|
|
f.Client = h.Client
|
|
f.ClientID = h.Client.GetID()
|
|
f.DeviceCodeRequestID = sqlxx.NullString(h.DeviceCodeRequestID)
|
|
f.DeviceHandledAt = sqlxx.NullTime(time.Now().UTC())
|
|
f.RequestedScope = h.RequestedScope
|
|
f.RequestedAudience = h.RequestedAudience
|
|
|
|
return nil
|
|
}
|
|
|
|
// InvalidateDeviceRequest shifts the flow state to DeviceFlowStateUsed. This
|
|
// transition is executed upon device completion.
|
|
func (f *Flow) InvalidateDeviceRequest() error {
|
|
if err := f.State.IsAny(DeviceFlowStateUnused); err != nil {
|
|
return err
|
|
}
|
|
f.State = DeviceFlowStateUsed
|
|
return nil
|
|
}
|
|
|
|
func (f *Flow) HandleLoginRequest(h *HandledLoginRequest) error {
|
|
if err := f.State.IsAny(FlowStateLoginInitialized, FlowStateLoginUnused, FlowStateLoginError); err != nil {
|
|
return err
|
|
}
|
|
|
|
if f.Subject != "" && h.Subject != "" && f.Subject != h.Subject {
|
|
return errors.Errorf("flow Subject %s does not match the HandledLoginRequest Subject %s", f.Subject, h.Subject)
|
|
}
|
|
|
|
if f.ForceSubjectIdentifier != "" && h.ForceSubjectIdentifier != "" && f.ForceSubjectIdentifier != h.ForceSubjectIdentifier {
|
|
return errors.Errorf("flow ForceSubjectIdentifier %s does not match the HandledLoginRequest ForceSubjectIdentifier %s", f.ForceSubjectIdentifier, h.ForceSubjectIdentifier)
|
|
}
|
|
|
|
f.State = FlowStateLoginUnused
|
|
|
|
if f.Context != nil {
|
|
f.Context = h.Context
|
|
}
|
|
|
|
f.Subject = h.Subject
|
|
f.ForceSubjectIdentifier = h.ForceSubjectIdentifier
|
|
|
|
f.IdentityProviderSessionID = sqlxx.NullString(h.IdentityProviderSessionID)
|
|
f.LoginRemember = h.Remember
|
|
f.LoginRememberFor = h.RememberFor
|
|
f.LoginExtendSessionLifespan = h.ExtendSessionLifespan
|
|
f.ACR = h.ACR
|
|
f.AMR = h.AMR
|
|
return nil
|
|
}
|
|
|
|
func (f *Flow) HandleLoginError(er *RequestDeniedError) error {
|
|
if err := f.State.IsAny(FlowStateLoginInitialized, FlowStateLoginUnused, FlowStateLoginError); err != nil {
|
|
return err
|
|
}
|
|
|
|
f.State = FlowStateLoginError
|
|
|
|
f.LoginError = er
|
|
|
|
// force-reset values
|
|
f.Subject = ""
|
|
f.ForceSubjectIdentifier = ""
|
|
f.LoginAuthenticatedAt = sqlxx.NullTime{}
|
|
f.IdentityProviderSessionID = ""
|
|
f.LoginRemember = false
|
|
f.LoginRememberFor = 0
|
|
f.LoginExtendSessionLifespan = false
|
|
f.ACR = ""
|
|
f.AMR = nil
|
|
|
|
return nil
|
|
}
|
|
|
|
func (f *Flow) GetLoginRequest() *LoginRequest {
|
|
return &LoginRequest{
|
|
ID: f.ID,
|
|
RequestedScope: f.RequestedScope,
|
|
RequestedAudience: f.RequestedAudience,
|
|
Skip: f.LoginSkip,
|
|
Subject: f.Subject,
|
|
OpenIDConnectContext: f.OpenIDConnectContext,
|
|
Client: f.Client,
|
|
RequestURL: f.RequestURL,
|
|
SessionID: f.SessionID,
|
|
}
|
|
}
|
|
|
|
// InvalidateLoginRequest shifts the flow state to FlowStateLoginUsed. This
|
|
// transition is executed upon login completion.
|
|
func (f *Flow) InvalidateLoginRequest() error {
|
|
if err := f.State.IsAny(FlowStateLoginUnused, FlowStateLoginError); err != nil {
|
|
return err
|
|
}
|
|
|
|
if f.State == FlowStateLoginUnused {
|
|
f.State = FlowStateLoginUsed
|
|
} else {
|
|
// FlowStateLoginError is already a terminal state, so we don't need to do anything here.
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (f *Flow) HandleConsentRequest(r *AcceptOAuth2ConsentRequest) error {
|
|
if err := f.State.IsAny(FlowStateConsentInitialized, FlowStateConsentUnused, FlowStateConsentError); err != nil {
|
|
return err
|
|
}
|
|
|
|
f.State = FlowStateConsentUnused
|
|
|
|
f.GrantedScope = r.GrantedScope
|
|
f.GrantedAudience = r.GrantedAudience
|
|
f.ConsentRemember = r.Remember
|
|
f.ConsentRememberFor = &r.RememberFor
|
|
f.ConsentHandledAt = sqlxx.NullTime(time.Now().UTC())
|
|
f.ConsentError = nil
|
|
if r.Context != nil {
|
|
f.Context = r.Context
|
|
}
|
|
|
|
if r.Session != nil {
|
|
f.SessionIDToken = r.Session.IDToken
|
|
f.SessionAccessToken = r.Session.AccessToken
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (f *Flow) HandleConsentError(er *RequestDeniedError) error {
|
|
if err := f.State.IsAny(FlowStateConsentInitialized, FlowStateConsentUnused, FlowStateConsentError); err != nil {
|
|
return err
|
|
}
|
|
|
|
f.State = FlowStateConsentError
|
|
|
|
f.ConsentError = er
|
|
f.ConsentHandledAt = sqlxx.NullTime(time.Now().UTC())
|
|
|
|
// force-reset values
|
|
f.GrantedScope = nil
|
|
f.GrantedAudience = nil
|
|
f.ConsentRemember = false
|
|
f.ConsentRememberFor = nil
|
|
|
|
return nil
|
|
}
|
|
|
|
func (f *Flow) InvalidateConsentRequest() error {
|
|
if err := f.State.IsAny(FlowStateConsentUnused, FlowStateConsentError); err != nil {
|
|
return err
|
|
}
|
|
|
|
if f.State == FlowStateConsentUnused {
|
|
f.State = FlowStateConsentUsed
|
|
} else {
|
|
// FlowStateConsentError is already a terminal state, so we don't need to do anything here.
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (f *Flow) GetConsentRequest(challenge string) *OAuth2ConsentRequest {
|
|
cs := OAuth2ConsentRequest{
|
|
Challenge: challenge,
|
|
ConsentRequestID: f.ConsentRequestID.String(),
|
|
RequestedScope: f.RequestedScope,
|
|
RequestedAudience: f.RequestedAudience,
|
|
Skip: f.ConsentSkip,
|
|
Subject: f.Subject,
|
|
OpenIDConnectContext: f.OpenIDConnectContext,
|
|
Client: f.Client,
|
|
RequestURL: f.RequestURL,
|
|
LoginChallenge: sqlxx.NullString(f.ID),
|
|
LoginSessionID: f.SessionID,
|
|
ACR: f.ACR,
|
|
AMR: f.AMR,
|
|
Context: f.Context,
|
|
}
|
|
// set some defaults for the API
|
|
if cs.RequestedAudience == nil {
|
|
cs.RequestedAudience = []string{}
|
|
}
|
|
if cs.AMR == nil {
|
|
cs.AMR = []string{}
|
|
}
|
|
return &cs
|
|
}
|
|
|
|
func (Flow) TableName() string {
|
|
return "hydra_oauth2_flow"
|
|
}
|
|
|
|
func (f *Flow) BeforeSave(_ *pop.Connection) error {
|
|
if f.Client != nil {
|
|
f.ClientID = f.Client.GetID()
|
|
}
|
|
if f.State == FlowStateLoginUnused && string(f.Context) == "" {
|
|
f.Context = sqlxx.JSONRawMessage("{}")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (f *Flow) AfterFind(c *pop.Connection) error {
|
|
// TODO Populate the client field in FindInDB and FindByConsentChallengeID in
|
|
// order to avoid accessing the database twice.
|
|
if f.ClientID == "" {
|
|
return nil
|
|
}
|
|
f.AfterSave(c)
|
|
f.Client = &client.Client{}
|
|
return sqlcon.HandleError(c.Where("id = ? AND nid = ?", f.ClientID, f.NID).First(f.Client))
|
|
}
|
|
|
|
func (f *Flow) AfterSave(_ *pop.Connection) {
|
|
if f.SessionAccessToken == nil {
|
|
f.SessionAccessToken = make(map[string]interface{})
|
|
}
|
|
if f.SessionIDToken == nil {
|
|
f.SessionIDToken = make(map[string]interface{})
|
|
}
|
|
}
|
|
|
|
type CipherProvider interface {
|
|
FlowCipher() *aead.XChaCha20Poly1305
|
|
}
|
|
|
|
// ToDeviceChallenge converts the flow into a device challenge.
|
|
func (f *Flow) ToDeviceChallenge(ctx context.Context, cipherProvider CipherProvider) (string, error) {
|
|
return Encode(ctx, cipherProvider.FlowCipher(), f, AsDeviceChallenge)
|
|
}
|
|
|
|
// ToDeviceVerifier converts the flow into a device verifier.
|
|
func (f *Flow) ToDeviceVerifier(ctx context.Context, cipherProvider CipherProvider) (string, error) {
|
|
return Encode(ctx, cipherProvider.FlowCipher(), f, AsDeviceVerifier)
|
|
}
|
|
|
|
// ToLoginChallenge converts the flow into a login challenge.
|
|
func (f Flow) ToLoginChallenge(ctx context.Context, cipherProvider CipherProvider) (challenge string, err error) {
|
|
if f.Client != nil {
|
|
f.ClientID = f.Client.GetID()
|
|
}
|
|
return Encode(ctx, cipherProvider.FlowCipher(), f, AsLoginChallenge)
|
|
}
|
|
|
|
// ToLoginVerifier converts the flow into a login verifier.
|
|
func (f Flow) ToLoginVerifier(ctx context.Context, cipherProvider CipherProvider) (verifier string, err error) {
|
|
if f.Client != nil {
|
|
f.ClientID = f.Client.GetID()
|
|
}
|
|
return Encode(ctx, cipherProvider.FlowCipher(), f, AsLoginVerifier)
|
|
}
|
|
|
|
// ToConsentChallenge converts the flow into a consent challenge.
|
|
func (f Flow) ToConsentChallenge(ctx context.Context, cipherProvider CipherProvider) (challenge string, err error) {
|
|
if f.Client != nil {
|
|
f.ClientID = f.Client.GetID()
|
|
}
|
|
return Encode(ctx, cipherProvider.FlowCipher(), f, AsConsentChallenge)
|
|
}
|
|
|
|
// ToConsentVerifier converts the flow into a consent verifier.
|
|
func (f Flow) ToConsentVerifier(ctx context.Context, cipherProvider CipherProvider) (verifier string, err error) {
|
|
if f.Client != nil {
|
|
f.ClientID = f.Client.GetID()
|
|
}
|
|
return Encode(ctx, cipherProvider.FlowCipher(), f, AsConsentVerifier)
|
|
}
|
|
|
|
func (f Flow) ToListConsentSessionResponse() *OAuth2ConsentSession {
|
|
s := &OAuth2ConsentSession{
|
|
ConsentRequestID: f.ConsentRequestID.String(),
|
|
GrantedScope: f.GrantedScope,
|
|
GrantedAudience: f.GrantedAudience,
|
|
RememberFor: pointerx.Deref(f.ConsentRememberFor),
|
|
Session: &AcceptOAuth2ConsentRequestSession{AccessToken: f.SessionAccessToken, IDToken: f.SessionIDToken},
|
|
Remember: f.ConsentRemember,
|
|
HandledAt: f.ConsentHandledAt,
|
|
Context: f.Context,
|
|
ConsentRequest: f.GetConsentRequest( /* No longer available and no longer needed: challenge = */ ""),
|
|
}
|
|
s.ConsentRequest.Client.Secret = "" // do not leak client secret in response
|
|
// set some defaults for the API
|
|
if s.GrantedAudience == nil {
|
|
s.GrantedAudience = []string{}
|
|
}
|
|
return s
|
|
}
|