diff --git a/internal/db/dbmigrate/scripts/v0.4.0.pre.sql b/internal/db/dbmigrate/scripts/v0.4.0.pre.sql index 8275d1c5e54cdf2343e34aa999c6bc87dc87ca67..345a187a61c38517e8d8fb008ce9027f0138ce16 100644 --- a/internal/db/dbmigrate/scripts/v0.4.0.pre.sql +++ b/internal/db/dbmigrate/scripts/v0.4.0.pre.sql @@ -1,69 +1,5 @@ # noinspection SqlResolveForFile -# noinspection SqlResolveForFile - -# noinspection SqlResolveForFile - -# noinspection SqlResolveForFile - -# noinspection SqlResolveForFile - -# noinspection SqlResolveForFile - -# noinspection SqlResolveForFile - -# noinspection SqlResolveForFile - -# noinspection SqlResolveForFile - -# noinspection SqlResolveForFile - -# noinspection SqlResolveForFile - -# noinspection SqlResolveForFile - -# noinspection SqlResolveForFile - -# noinspection SqlResolveForFile - -# noinspection SqlResolveForFile - -# noinspection SqlResolveForFile - -# noinspection SqlResolveForFile - -# noinspection SqlResolveForFile - -# noinspection SqlResolveForFile - -# noinspection SqlResolveForFile - -# noinspection SqlResolveForFile - -# noinspection SqlResolveForFile - -# noinspection SqlResolveForFile - -# noinspection SqlResolveForFile - -# noinspection SqlResolveForFile - -# noinspection SqlResolveForFile - -# noinspection SqlResolveForFile - -# noinspection SqlResolveForFile - -# noinspection SqlResolveForFile - -# noinspection SqlResolveForFile - -# noinspection SqlResolveForFile - -# noinspection SqlResolveForFile - -# noinspection SqlResolveForFile - # Tables DROP VIEW IF EXISTS MyTokens; @@ -75,6 +11,26 @@ ALTER TABLE Users ALTER TABLE TransferCodesAttributes ADD ssh_key_hash VARCHAR(128) NULL; +TRUNCATE TABLE AuthInfo; +ALTER TABLE AuthInfo + ADD request_json JSON NOT NULL; +ALTER TABLE AuthInfo + DROP COLUMN iss; +ALTER TABLE AuthInfo + DROP COLUMN restrictions; +ALTER TABLE AuthInfo + DROP COLUMN capabilities; +ALTER TABLE AuthInfo + DROP COLUMN name; +ALTER TABLE AuthInfo + DROP COLUMN subtoken_capabilities; +ALTER TABLE AuthInfo + DROP COLUMN rotation; +ALTER TABLE AuthInfo + DROP COLUMN response_type; +ALTER TABLE AuthInfo + DROP COLUMN max_token_len; + # CryptStore CREATE TABLE `CryptPayloadTypes` ( @@ -260,31 +216,19 @@ END;; CREATE OR REPLACE PROCEDURE AuthInfo_Get(IN STATE TEXT) BEGIN SELECT state_h, - iss, - restrictions, - capabilities, - subtoken_capabilities, - name, + request_json, polling_code, - rotation, - response_type, - max_token_len, code_verifier FROM AuthInfo WHERE state_h = STATE AND expires_at >= CURRENT_TIMESTAMP(); END;; -CREATE OR REPLACE PROCEDURE AuthInfo_Insert(IN STATE_H_ VARCHAR(128), IN ISS_ TEXT, IN RESTRICTIONS_ LONGTEXT, - IN CAPABILITIES_ LONGTEXT, IN SUBTOKEN_CAPABILITIES_ LONGTEXT, - IN NAME_ TEXT, - IN EXPIRES_IN_ INT, IN POLLING_CODE_ BIT, IN ROTATION_ LONGTEXT, - IN RESPONSE_TYPE_ VARCHAR(128), IN MAX_TOKEN_LEN_ INT) +CREATE OR REPLACE PROCEDURE AuthInfo_Insert(IN STATE_H_ VARCHAR(128), IN REQUEST LONGTEXT, + IN EXPIRES_IN_ INT, IN POLLING_CODE_ BIT) BEGIN - INSERT INTO AuthInfo (`state_h`, `iss`, `restrictions`, `capabilities`, `subtoken_capabilities`, `name`, - `expires_in`, `polling_code`, `rotation`, `response_type`, `max_token_len`) - VALUES (STATE_H_, ISS_, RESTRICTIONS_, CAPABILITIES_, SUBTOKEN_CAPABILITIES_, NAME_, EXPIRES_IN_, POLLING_CODE_, - ROTATION_, RESPONSE_TYPE_, MAX_TOKEN_LEN_); + INSERT INTO AuthInfo (`state_h`, `request_json`, `expires_in`, `polling_code`) + VALUES (STATE_H_, REQUEST, EXPIRES_IN_, POLLING_CODE_); END;; CREATE OR REPLACE PROCEDURE AuthInfo_SetCodeVerifier(IN STATE TEXT, IN VERIFIER TEXT) @@ -292,15 +236,10 @@ BEGIN UPDATE AuthInfo SET code_verifier = VERIFIER WHERE state_h = STATE; END;; -CREATE OR REPLACE PROCEDURE AuthInfo_Update(IN STATE TEXT, IN RESTRICTIONS_ LONGTEXT, IN CAPABILITIES_ LONGTEXT, - IN SUBTOKEN_CAPABILITIES_ LONGTEXT, IN ROTATION_ LONGTEXT, IN NAME_ TEXT) +CREATE OR REPLACE PROCEDURE AuthInfo_Update(IN STATE TEXT, IN REQUEST LONGTEXT) BEGIN UPDATE AuthInfo - SET restrictions = RESTRICTIONS_, - capabilities = CAPABILITIES_, - subtoken_capabilities = SUBTOKEN_CAPABILITIES_, - rotation = ROTATION_, - name = NAME_ + SET request_json = REQUEST WHERE state_h = STATE; END;; diff --git a/internal/db/dbrepo/authcodeinforepo/authcodeInfo.go b/internal/db/dbrepo/authcodeinforepo/authcodeInfo.go index 9f944f8d43305163808ed1b520a92aec31eace77..535ff71a95192933c3926a2af8ba0747f2475f39 100644 --- a/internal/db/dbrepo/authcodeinforepo/authcodeInfo.go +++ b/internal/db/dbrepo/authcodeinforepo/authcodeInfo.go @@ -5,12 +5,11 @@ import ( "github.com/pkg/errors" log "github.com/sirupsen/logrus" - "github.com/oidc-mytoken/server/shared/model" - "github.com/oidc-mytoken/server/shared/utils" + "github.com/oidc-mytoken/server/internal/config" + "github.com/oidc-mytoken/server/internal/endpoints/token/mytoken/pkg" "github.com/oidc-mytoken/api/v0" - "github.com/oidc-mytoken/server/internal/config" "github.com/oidc-mytoken/server/internal/db" "github.com/oidc-mytoken/server/internal/db/dbrepo/authcodeinforepo/state" "github.com/oidc-mytoken/server/internal/db/dbrepo/mytokenrepo/transfercoderepo" @@ -25,67 +24,34 @@ type AuthFlowInfo struct { // AuthFlowInfoOut holds database information about a started authorization flow type AuthFlowInfoOut struct { - State *state.State - Issuer string - Restrictions restrictions.Restrictions - Capabilities api.Capabilities - SubtokenCapabilities api.Capabilities - Name string - PollingCode bool - Rotation *api.Rotation - ResponseType model.ResponseType - MaxTokenLen int - CodeVerifier string + State *state.State + pkg.AuthCodeFlowRequest + PollingCode bool + CodeVerifier string } type authFlowInfo struct { - State *state.State `db:"state_h"` - Issuer string `db:"iss"` - Restrictions restrictions.Restrictions - Capabilities api.Capabilities - SubtokenCapabilities api.Capabilities `db:"subtoken_capabilities"` - Name db.NullString - PollingCode db.BitBool `db:"polling_code"` - ExpiresIn int64 `db:"expires_in"` - Rotation *api.Rotation - ResponseType model.ResponseType `db:"response_type"` - MaxTokenLen *int `db:"max_token_len"` - CodeVerifier db.NullString `db:"code_verifier"` + State *state.State `db:"state_h"` + pkg.AuthCodeFlowRequest `db:"request_json"` + PollingCode db.BitBool `db:"polling_code"` + CodeVerifier db.NullString `db:"code_verifier"` } func (i *AuthFlowInfo) toAuthFlowInfo() *authFlowInfo { return &authFlowInfo{ - State: i.State, - Issuer: i.Issuer, - Restrictions: i.Restrictions, - Capabilities: i.Capabilities, - SubtokenCapabilities: i.SubtokenCapabilities, - Name: db.NewNullString(i.Name), - ExpiresIn: config.Get().Features.Polling.PollingCodeExpiresAfter, - PollingCode: i.PollingCode != nil, - Rotation: i.Rotation, - ResponseType: i.ResponseType, - MaxTokenLen: utils.NewInt(i.MaxTokenLen), + State: i.State, + AuthCodeFlowRequest: i.AuthCodeFlowRequest, + PollingCode: i.PollingCode != nil, } } func (i *authFlowInfo) toAuthFlowInfo() *AuthFlowInfoOut { - o := &AuthFlowInfoOut{ - State: i.State, - Issuer: i.Issuer, - Restrictions: i.Restrictions, - Capabilities: i.Capabilities, - SubtokenCapabilities: i.SubtokenCapabilities, - Name: i.Name.String, - PollingCode: bool(i.PollingCode), - Rotation: i.Rotation, - ResponseType: i.ResponseType, - CodeVerifier: i.CodeVerifier.String, + return &AuthFlowInfoOut{ + State: i.State, + AuthCodeFlowRequest: i.AuthCodeFlowRequest, + PollingCode: bool(i.PollingCode), + CodeVerifier: i.CodeVerifier.String, } - if i.MaxTokenLen != nil { - o.MaxTokenLen = *i.MaxTokenLen - } - return o } // Store stores the AuthFlowInfoIn in the database as well as the linked polling code if it exists @@ -99,9 +65,9 @@ func (i *AuthFlowInfo) Store(rlog log.Ext1FieldLogger, tx *sqlx.Tx) error { return err } } - _, err := tx.NamedExec( - `CALL AuthInfo_Insert(:state_h, :iss, :restrictions, :capabilities, :subtoken_capabilities, :name, :expires_in, :polling_code, :rotation, :response_type, :max_token_len)`, - store, + _, err := tx.Exec( + `CALL AuthInfo_Insert(?, ?, ?, ?)`, store.State, store.AuthCodeFlowRequest, + config.Get().Features.Polling.PollingCodeExpiresAfter, store.PollingCode, ) return errors.WithStack(err) }, @@ -113,7 +79,15 @@ func GetAuthFlowInfoByState(rlog log.Ext1FieldLogger, state *state.State) (*Auth info := authFlowInfo{} if err := db.Transact( rlog, func(tx *sqlx.Tx) error { - return errors.WithStack(tx.Get(&info, `CALL AuthInfo_Get(?)`, state)) + row := tx.QueryRowx(`CALL AuthInfo_Get(?)`, state) + if err := row.Err(); err != nil { + return errors.WithStack(err) + } + return errors.WithStack( + row.Scan( + &info.State, &info.AuthCodeFlowRequest, &info.PollingCode, &info.CodeVerifier, + ), + ) }, ); err != nil { return nil, err @@ -138,9 +112,18 @@ func UpdateTokenInfoByState( ) error { return db.RunWithinTransaction( rlog, tx, func(tx *sqlx.Tx) error { - _, err := tx.Exec( - `CALL AuthInfo_Update(?,?,?,?,?,?)`, - state, r, c, sc, rot, tokenName, + info, err := GetAuthFlowInfoByState(rlog, state) + if err != nil { + return err + } + info.Restrictions = r + info.Capabilities = c + info.SubtokenCapabilities = sc + info.Rotation = rot + info.Name = tokenName + _, err = tx.Exec( + `CALL AuthInfo_Update(?,?)`, + state, info.AuthCodeFlowRequest, ) return errors.WithStack(err) }, diff --git a/internal/endpoints/token/mytoken/pkg/authCodeFlowRequest.go b/internal/endpoints/token/mytoken/pkg/authCodeFlowRequest.go index e3f0a19147e66dcf99f9bf0831d008c0e1b17a66..cf1be4cf5fe11ab00981dcf50ebcbeffccb21359 100644 --- a/internal/endpoints/token/mytoken/pkg/authCodeFlowRequest.go +++ b/internal/endpoints/token/mytoken/pkg/authCodeFlowRequest.go @@ -1,6 +1,7 @@ package pkg import ( + "database/sql/driver" "encoding/json" "github.com/oidc-mytoken/api/v0" @@ -11,6 +12,7 @@ import ( type AuthCodeFlowRequest struct { OIDCFlowRequest RedirectType string `json:"redirect_type"` + RedirectURL string `json:"redirect_url"` } // Native checks if the request is native @@ -31,6 +33,22 @@ func (r *AuthCodeFlowRequest) UnmarshalJSON(data []byte) error { // MarshalJSON implements the json marshaler interface func (r *AuthCodeFlowRequest) MarshalJSON() ([]byte, error) { r.redirectType = r.RedirectType + r.redirectURL = r.RedirectURL data, err := json.Marshal(r.OIDCFlow) return data, errors.WithStack(err) } + +// Scan implements the sql.Scanner interface +func (r *AuthCodeFlowRequest) Scan(src interface{}) error { + v, ok := src.([]byte) + if !ok { + return errors.New("bad []byte type assertion") + } + return errors.WithStack(json.Unmarshal(v, r)) +} + +// Value implements the driver.Valuer interface +func (r AuthCodeFlowRequest) Value() (driver.Value, error) { + v, err := json.Marshal(r) + return v, errors.WithStack(err) +} diff --git a/internal/endpoints/token/mytoken/pkg/oidcFlowRequest.go b/internal/endpoints/token/mytoken/pkg/oidcFlowRequest.go index 6299b6265b4172aa4e2c2d39f3426d5defceec78..39de8c04e30ca00975807421f27f0ea48f414224 100644 --- a/internal/endpoints/token/mytoken/pkg/oidcFlowRequest.go +++ b/internal/endpoints/token/mytoken/pkg/oidcFlowRequest.go @@ -19,6 +19,7 @@ type OIDCFlowRequest struct { Restrictions restrictions.Restrictions `json:"restrictions"` ResponseType model.ResponseType `json:"response_type"` redirectType string + redirectURL string } // NewOIDCFlowRequest creates a new OIDCFlowRequest with default values where they can be omitted @@ -40,15 +41,22 @@ func (r *OIDCFlowRequest) SetRedirectType(redirect string) { r.redirectType = redirect } +// SetRedirectURL sets the (hidden) redirect url +func (r *OIDCFlowRequest) SetRedirectURL(url string) { + r.redirectURL = url +} + // MarshalJSON implements the json.Marshaler interface func (r OIDCFlowRequest) MarshalJSON() ([]byte, error) { type ofr OIDCFlowRequest o := struct { ofr RedirectType string `json:"redirect_type,omitempty"` + RedirectURL string `json:"redirect_url,omitempty"` }{ ofr: ofr(r), RedirectType: r.redirectType, + RedirectURL: r.redirectURL, } data, err := json.Marshal(o) return data, errors.WithStack(err) @@ -60,14 +68,17 @@ func (r *OIDCFlowRequest) UnmarshalJSON(data []byte) error { o := struct { ofr RedirectType string `json:"redirect_type"` + RedirectURL string `json:"redirect_url"` }{ ofr: ofr(*NewOIDCFlowRequest()), } o.RedirectType = o.redirectType + o.RedirectURL = o.redirectURL if err := json.Unmarshal(data, &o); err != nil { return errors.WithStack(err) } o.redirectType = o.RedirectType + o.redirectURL = o.RedirectURL *r = OIDCFlowRequest(o.ofr) if r.SubtokenCapabilities != nil && !r.Capabilities.Has(api.CapabilityCreateMT) { r.SubtokenCapabilities = nil @@ -80,6 +91,7 @@ func (r OIDCFlowRequest) ToAuthCodeFlowRequest() AuthCodeFlowRequest { return AuthCodeFlowRequest{ OIDCFlowRequest: r, RedirectType: r.redirectType, + RedirectURL: r.redirectURL, } } diff --git a/internal/oidc/authcode/authcode.go b/internal/oidc/authcode/authcode.go index 2ee989484c8b27f036b2a8fffa4387bbc67b4003..29558e66d620c09a3e214f29507550ee57c49d4f 100644 --- a/internal/oidc/authcode/authcode.go +++ b/internal/oidc/authcode/authcode.go @@ -34,6 +34,7 @@ import ( "github.com/oidc-mytoken/server/shared/utils" "github.com/oidc-mytoken/server/shared/utils/issuerUtils" "github.com/oidc-mytoken/server/shared/utils/jwtutils" + "github.com/oidc-mytoken/server/shared/utils/ternary" "github.com/oidc-mytoken/server/shared/utils/unixtime" ) @@ -103,6 +104,7 @@ func StartAuthCodeFlow(ctx *fiber.Ctx, oidcReq response.OIDCFlowRequest) *model. Response: api.ErrorUnknownIssuer, } } + req.Issuer = provider.Issuer exp := req.Restrictions.GetExpires() if exp > 0 && exp < unixtime.Now() { return &model.Response{ @@ -112,19 +114,11 @@ func StartAuthCodeFlow(ctx *fiber.Ctx, oidcReq response.OIDCFlowRequest) *model. } oState, consentCode := state.CreateState() - authFlowInfoO := authcodeinforepo.AuthFlowInfoOut{ - State: oState, - Issuer: provider.Issuer, - Restrictions: req.Restrictions, - Capabilities: req.Capabilities, - SubtokenCapabilities: req.SubtokenCapabilities, - Name: req.Name, - Rotation: req.Rotation, - ResponseType: req.ResponseType, - MaxTokenLen: req.MaxTokenLen, - } authFlowInfo := authcodeinforepo.AuthFlowInfo{ - AuthFlowInfoOut: authFlowInfoO, + AuthFlowInfoOut: authcodeinforepo.AuthFlowInfoOut{ + State: oState, + AuthCodeFlowRequest: req, + }, } res := api.AuthCodeFlowResponse{ AuthorizationURL: utils.CombineURLPath(consentEndpoint, consentCode.String()), @@ -273,7 +267,7 @@ func CodeExchange( } return &model.Response{ Status: fiber.StatusSeeOther, - Response: "/home", + Response: ternary.IfNotEmptyOr(authInfo.RedirectURL, "/home"), Cookies: []*fiber.Cookie{cookie}, } } diff --git a/internal/server/web/static/js/login.js b/internal/server/web/static/js/login.js index 22622a92e6aedd98ce4af29c14f22108c8b81ed0..0c0abcaba1aa14dfc7e62e3ded12afff2446a61b 100644 --- a/internal/server/web/static/js/login.js +++ b/internal/server/web/static/js/login.js @@ -26,8 +26,10 @@ $('#login-form').on('submit', function(e){ ] data['rotation'] = { "on_other": true, - "lifetime": 3600*24, + "lifetime": 3600 * 24, } + data['redirect_type'] = 'web'; + data['redirect_url'] = '/home'; data['name'] = "mytoken-web"; storageSet("oidc_issuer", data["oidc_issuer"], true); data = JSON.stringify(data); @@ -35,7 +37,7 @@ $('#login-form').on('submit', function(e){ type: "POST", url: storageGet("mytoken_endpoint"), data: data, - success: function(res){ + success: function (res) { window.location.href = res['authorization_url']; }, dataType: "json",