From 12c4ae2dee87105a6bae481a9b387f01bc770dfe Mon Sep 17 00:00:00 2001 From: Sam Lucidi Date: Fri, 28 Apr 2023 16:13:54 -0400 Subject: [PATCH] Support refreshing auth tokens Signed-off-by: Sam Lucidi --- api/auth.go | 43 ++++++++++++++++++++++++++++++++++++++++--- auth/builtin.go | 18 ++++++++++++++++-- auth/keycloak.go | 25 +++++++++++++++++++++++-- auth/provider.go | 10 +++++++++- 4 files changed, 88 insertions(+), 8 deletions(-) diff --git a/api/auth.go b/api/auth.go index 67289738..bf6b49de 100644 --- a/api/auth.go +++ b/api/auth.go @@ -9,8 +9,9 @@ import ( // // Routes const ( - AuthRoot = "/auth" - AuthLoginRoot = AuthRoot + "/login" + AuthRoot = "/auth" + AuthLoginRoot = AuthRoot + "/login" + AuthRefreshRoot = AuthRoot + "/refresh" ) // @@ -23,6 +24,7 @@ type AuthHandler struct { // AddRoutes adds routes. func (h AuthHandler) AddRoutes(e *gin.Engine) { e.POST(AuthLoginRoot, h.Login) + e.POST(AuthRefreshRoot, h.Refresh) } // Login godoc @@ -39,7 +41,7 @@ func (h AuthHandler) Login(ctx *gin.Context) { _ = ctx.Error(err) return } - r.Token, err = auth.Remote.Login(r.User, r.Password) + token, err := auth.Remote.Login(r.User, r.Password) if err != nil { h.Render(ctx, http.StatusUnauthorized, @@ -49,6 +51,39 @@ func (h AuthHandler) Login(ctx *gin.Context) { return } r.Password = "" // Clear out password from response + r.Token = token.Access + r.Refresh = token.Refresh + r.Expiry = token.Expiry + h.Render(ctx, http.StatusCreated, r) +} + +// Refresh godoc +// @summary Refresh bearer token. +// @description Refresh bearer token. +// @tags auth +// @produce json +// @success 201 {object} api.Login +// @router /auth/refresh [post] +func (h AuthHandler) Refresh(ctx *gin.Context) { + r := &Login{} + err := h.Bind(ctx, r) + if err != nil { + _ = ctx.Error(err) + return + } + token, err := auth.Remote.Refresh(r.Refresh) + if err != nil { + h.Render(ctx, + http.StatusUnauthorized, + gin.H{ + "error": err.Error(), + }) + return + } + r.Password = "" // Clear out password from response + r.Token = token.Access + r.Refresh = token.Refresh + r.Expiry = token.Expiry h.Render(ctx, http.StatusCreated, r) } @@ -58,6 +93,8 @@ type Login struct { User string `json:"user"` Password string `json:"password,omitempty"` Token string `json:"token"` + Refresh string `json:"refresh"` + Expiry int `json:"expiry"` } // diff --git a/auth/builtin.go b/auth/builtin.go index bf69b285..0902369e 100644 --- a/auth/builtin.go +++ b/auth/builtin.go @@ -51,8 +51,15 @@ func (r *NoAuth) User(jwToken *jwt.Token) (name string) { return } +// // Login and obtain a token. -func (r *NoAuth) Login(user, password string) (token string, err error) { +func (r *NoAuth) Login(user, password string) (token Token, err error) { + return +} + +// +// Refresh token. +func (r *NoAuth) Refresh(refresh string) (token Token, err error) { return } @@ -140,8 +147,15 @@ func (r *Builtin) User(jwToken *jwt.Token) (user string) { return } +// // Login and obtain a token. -func (r *Builtin) Login(user, password string) (token string, err error) { +func (r *Builtin) Login(user, password string) (token Token, err error) { + return +} + +// +// Refresh token. +func (r *Builtin) Refresh(refresh string) (token Token, err error) { return } diff --git a/auth/keycloak.go b/auth/keycloak.go index 1eb45700..de4c0954 100644 --- a/auth/keycloak.go +++ b/auth/keycloak.go @@ -37,8 +37,9 @@ func (r Keycloak) NewToken(user string, scopes []string, claims jwt.MapClaims) ( return } +// // Login and obtain a token. -func (r *Keycloak) Login(user, password string) (token string, err error) { +func (r *Keycloak) Login(user, password string) (token Token, err error) { jwt, err := r.client.Login( context.TODO(), Settings.Auth.Keycloak.ClientID, @@ -48,7 +49,27 @@ func (r *Keycloak) Login(user, password string) (token string, err error) { password, ) if err == nil { - token = jwt.AccessToken + token.Access = jwt.AccessToken + token.Refresh = jwt.RefreshToken + token.Expiry = jwt.ExpiresIn + } + return +} + +// +// Refresh token. +func (r *Keycloak) Refresh(refresh string) (token Token, err error) { + jwt, err := r.client.RefreshToken( + context.TODO(), + refresh, + Settings.Auth.Keycloak.ClientID, + Settings.Auth.Keycloak.ClientSecret, + Settings.Auth.Keycloak.Realm, + ) + if err == nil { + token.Access = jwt.AccessToken + token.Refresh = jwt.RefreshToken + token.Expiry = jwt.ExpiresIn } return } diff --git a/auth/provider.go b/auth/provider.go index a70fcff7..78def4e6 100644 --- a/auth/provider.go +++ b/auth/provider.go @@ -33,7 +33,15 @@ type Provider interface { // User extracts the user from token. User(jwToken *jwt.Token) (user string) // Login and obtain a token. - Login(user, password string) (token string, err error) + Login(user, password string) (token Token, err error) + // Refresh token. + Refresh(refresh string) (token Token, err error) +} + +type Token struct { + Access string + Refresh string + Expiry int } //