logout: add support of logout flow
This commit is contained in:
51
internal/hydra/consent.go
Normal file
51
internal/hydra/consent.go
Normal file
@ -0,0 +1,51 @@
|
||||
/*
|
||||
Copyright (C) JSC iCore - All Rights Reserved
|
||||
|
||||
Unauthorized copying of this file, via any medium is strictly prohibited
|
||||
Proprietary and confidential
|
||||
*/
|
||||
|
||||
package hydra
|
||||
|
||||
import (
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// ConsentReqDoer fetches information on the OAuth2 request and then accept or reject the requested authentication process.
|
||||
type ConsentReqDoer struct {
|
||||
hydraURL string
|
||||
rememberFor int
|
||||
}
|
||||
|
||||
// NewConsentReqDoer creates a ConsentRequest.
|
||||
func NewConsentReqDoer(hydraURL string, rememberFor int) *ConsentReqDoer {
|
||||
return &ConsentReqDoer{hydraURL: hydraURL, rememberFor: rememberFor}
|
||||
}
|
||||
|
||||
// InitiateRequest fetches information on the OAuth2 request.
|
||||
func (crd *ConsentReqDoer) InitiateRequest(challenge string) (*ReqInfo, error) {
|
||||
ri, err := initiateRequest(consent, crd.hydraURL, challenge)
|
||||
return ri, errors.Wrap(err, "failed to initiate consent request")
|
||||
}
|
||||
|
||||
// AcceptConsentRequest accepts the requested authentication process, and returns redirect URI.
|
||||
func (crd *ConsentReqDoer) AcceptConsentRequest(challenge string, remember bool, grantScope []string, idToken interface{}) (string, error) {
|
||||
type session struct {
|
||||
IDToken interface{} `json:"id_token,omitempty"`
|
||||
}
|
||||
data := struct {
|
||||
GrantScope []string `json:"grant_scope"`
|
||||
Remember bool `json:"remember"`
|
||||
RememberFor int `json:"remember_for"`
|
||||
Session session `json:"session,omitempty"`
|
||||
}{
|
||||
GrantScope: grantScope,
|
||||
Remember: remember,
|
||||
RememberFor: crd.rememberFor,
|
||||
Session: session{
|
||||
IDToken: idToken,
|
||||
},
|
||||
}
|
||||
redirectURI, err := acceptRequest(consent, crd.hydraURL, challenge, data)
|
||||
return redirectURI, errors.Wrap(err, "failed to accept consent request")
|
||||
}
|
151
internal/hydra/hydra.go
Normal file
151
internal/hydra/hydra.go
Normal file
@ -0,0 +1,151 @@
|
||||
/*
|
||||
Copyright (C) JSC iCore - All Rights Reserved
|
||||
|
||||
Unauthorized copying of this file, via any medium is strictly prohibited
|
||||
Proprietary and confidential
|
||||
*/
|
||||
|
||||
package hydra
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrUnauthenticated is an error that happens when authentication is failed.
|
||||
ErrUnauthenticated = errors.New("unauthenticated")
|
||||
// ErrChallengeNotFound is an error that happens when an unknown challenge is used.
|
||||
ErrChallengeNotFound = errors.New("challenge not found")
|
||||
// ErrChallengeExpired is an error that happens when a challenge is already used.
|
||||
ErrChallengeExpired = errors.New("challenge expired")
|
||||
)
|
||||
|
||||
type reqType string
|
||||
|
||||
const (
|
||||
login reqType = "login"
|
||||
consent reqType = "consent"
|
||||
logout reqType = "logout"
|
||||
)
|
||||
|
||||
// ReqInfo contains information on an ongoing login or consent request.
|
||||
type ReqInfo struct {
|
||||
Challenge string `json:"challenge"`
|
||||
RequestedScopes []string `json:"requested_scope"`
|
||||
Skip bool `json:"skip"`
|
||||
Subject string `json:"subject"`
|
||||
}
|
||||
|
||||
func initiateRequest(typ reqType, hydraURL, challenge string) (*ReqInfo, error) {
|
||||
ref, err := url.Parse(fmt.Sprintf("oauth2/auth/requests/%[1]s?%[1]s_challenge=%s", string(typ), challenge))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
u, err := parseURL(hydraURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
u = u.ResolveReference(ref)
|
||||
|
||||
resp, err := http.Get(u.String())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = checkResponse(resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var ri ReqInfo
|
||||
if err := json.Unmarshal(data, &ri); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &ri, nil
|
||||
}
|
||||
|
||||
func acceptRequest(typ reqType, hydraURL, challenge string, data interface{}) (string, error) {
|
||||
ref, err := url.Parse(fmt.Sprintf("oauth2/auth/requests/%[1]s/accept?%[1]s_challenge=%s", string(typ), challenge))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
u, err := parseURL(hydraURL)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
u = u.ResolveReference(ref)
|
||||
|
||||
var body []byte
|
||||
if data != nil {
|
||||
if body, err = json.Marshal(data); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
r, err := http.NewRequest(http.MethodPut, u.String(), bytes.NewBuffer(body))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
r.Header.Set("Content-Type", "application/json")
|
||||
resp, err := http.DefaultClient.Do(r)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if err := checkResponse(resp); err != nil {
|
||||
return "", err
|
||||
}
|
||||
var rs struct {
|
||||
RedirectTo string `json:"redirect_to"`
|
||||
}
|
||||
dec := json.NewDecoder(resp.Body)
|
||||
if err := dec.Decode(&rs); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return rs.RedirectTo, nil
|
||||
}
|
||||
|
||||
func checkResponse(resp *http.Response) error {
|
||||
if resp.StatusCode >= 200 && resp.StatusCode <= 302 {
|
||||
return nil
|
||||
}
|
||||
|
||||
switch resp.StatusCode {
|
||||
case 401:
|
||||
return ErrUnauthenticated
|
||||
case 404:
|
||||
return ErrChallengeNotFound
|
||||
case 409:
|
||||
return ErrChallengeExpired
|
||||
default:
|
||||
var rs struct {
|
||||
Message string `json:"error"`
|
||||
}
|
||||
data, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := json.Unmarshal(data, &rs); err != nil {
|
||||
return err
|
||||
}
|
||||
return fmt.Errorf("bad HTTP status code %d with message %q", resp.StatusCode, rs.Message)
|
||||
}
|
||||
}
|
||||
|
||||
func parseURL(s string) (*url.URL, error) {
|
||||
if len(s) > 0 && s[len(s)-1] != '/' {
|
||||
s += "/"
|
||||
}
|
||||
u, err := url.Parse(s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return u, nil
|
||||
}
|
44
internal/hydra/login.go
Normal file
44
internal/hydra/login.go
Normal file
@ -0,0 +1,44 @@
|
||||
/*
|
||||
Copyright (C) JSC iCore - All Rights Reserved
|
||||
|
||||
Unauthorized copying of this file, via any medium is strictly prohibited
|
||||
Proprietary and confidential
|
||||
*/
|
||||
|
||||
package hydra
|
||||
|
||||
import (
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// LoginReqDoer fetches information on the OAuth2 request and then accept or reject the requested authentication process.
|
||||
type LoginReqDoer struct {
|
||||
hydraURL string
|
||||
rememberFor int
|
||||
}
|
||||
|
||||
// NewLoginReqDoer creates a LoginRequest.
|
||||
func NewLoginReqDoer(hydraURL string, rememberFor int) *LoginReqDoer {
|
||||
return &LoginReqDoer{hydraURL: hydraURL, rememberFor: rememberFor}
|
||||
}
|
||||
|
||||
// InitiateRequest fetches information on the OAuth2 request.
|
||||
func (lrd *LoginReqDoer) InitiateRequest(challenge string) (*ReqInfo, error) {
|
||||
ri, err := initiateRequest(login, lrd.hydraURL, challenge)
|
||||
return ri, errors.Wrap(err, "failed to initiate login request")
|
||||
}
|
||||
|
||||
// AcceptLoginRequest accepts the requested authentication process, and returns redirect URI.
|
||||
func (lrd *LoginReqDoer) AcceptLoginRequest(challenge string, remember bool, subject string) (string, error) {
|
||||
data := struct {
|
||||
Remember bool `json:"remember"`
|
||||
RememberFor int `json:"remember_for"`
|
||||
Subject string `json:"subject"`
|
||||
}{
|
||||
Remember: remember,
|
||||
RememberFor: lrd.rememberFor,
|
||||
Subject: subject,
|
||||
}
|
||||
redirectURI, err := acceptRequest(login, lrd.hydraURL, challenge, data)
|
||||
return redirectURI, errors.Wrap(err, "failed to accept login request")
|
||||
}
|
34
internal/hydra/logout.go
Normal file
34
internal/hydra/logout.go
Normal file
@ -0,0 +1,34 @@
|
||||
/*
|
||||
Copyright (C) JSC iCore - All Rights Reserved
|
||||
|
||||
Unauthorized copying of this file, via any medium is strictly prohibited
|
||||
Proprietary and confidential
|
||||
*/
|
||||
|
||||
package hydra
|
||||
|
||||
import (
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// LogoutReqDoer fetches information on the OAuth2 request and then accepts or rejects the requested logout process.
|
||||
type LogoutReqDoer struct {
|
||||
hydraURL string
|
||||
}
|
||||
|
||||
// NewLogoutReqDoer creates a LogoutRequest.
|
||||
func NewLogoutReqDoer(hydraURL string) *LogoutReqDoer {
|
||||
return &LogoutReqDoer{hydraURL: hydraURL}
|
||||
}
|
||||
|
||||
// InitiateRequest fetches information on the OAuth2 request.
|
||||
func (lrd *LogoutReqDoer) InitiateRequest(challenge string) (*ReqInfo, error) {
|
||||
ri, err := initiateRequest(logout, lrd.hydraURL, challenge)
|
||||
return ri, errors.Wrap(err, "failed to initiate logout request")
|
||||
}
|
||||
|
||||
// AcceptLogoutRequest accepts the requested logout process, and returns redirect URI.
|
||||
func (lrd *LogoutReqDoer) AcceptLogoutRequest(challenge string) (string, error) {
|
||||
redirectURI, err := acceptRequest(logout, lrd.hydraURL, challenge, nil)
|
||||
return redirectURI, errors.Wrap(err, "failed to accept logout request")
|
||||
}
|
Reference in New Issue
Block a user