You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@devlake.apache.org by li...@apache.org on 2023/05/15 08:19:58 UTC
[incubator-devlake] branch main updated: Refresh token (#5174)
This is an automated email from the ASF dual-hosted git repository.
likyh pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/incubator-devlake.git
The following commit(s) were added to refs/heads/main by this push:
new 3281ba053 Refresh token (#5174)
3281ba053 is described below
commit 3281ba05336c8ee2efe05725a6a128897748e195
Author: Klesh Wong <zh...@merico.dev>
AuthorDate: Mon May 15 16:19:53 2023 +0800
Refresh token (#5174)
* feat: BE supports token refreshing
* feat: FE supports token refreshing
* fix: make swag failed
* fix: remove catch to avoid unwanted behvior
---
backend/server/api/api.go | 1 +
backend/server/api/login/login.go | 28 ++++++++++++++++++++++++--
backend/server/services/auth/auth.go | 5 +++++
backend/server/services/auth/cognito.go | 32 +++++++++++++++++++++++++-----
config-ui/src/pages/login/login.tsx | 1 +
config-ui/src/utils/request.ts | 35 +++++++++++++++++++++++++++++++++
6 files changed, 95 insertions(+), 7 deletions(-)
diff --git a/backend/server/api/api.go b/backend/server/api/api.go
index c9bf7d93e..be830bb10 100644
--- a/backend/server/api/api.go
+++ b/backend/server/api/api.go
@@ -74,6 +74,7 @@ func CreateApiService() {
// Add login endpoint
router.POST("/login", login.Login)
router.POST("/login/newpassword", login.NewPassword)
+ router.POST("/login/refreshtoken", login.RefreshToken)
// Use AuthenticationMiddleware for protected routes
router.Use(auth.Middleware)
}
diff --git a/backend/server/api/login/login.go b/backend/server/api/login/login.go
index 6b71ef41d..7ef7f6d45 100644
--- a/backend/server/api/login/login.go
+++ b/backend/server/api/login/login.go
@@ -32,7 +32,7 @@ import (
// @Tags framework/login
// @Accept application/json
// @Param login body auth.LoginRequest true "json"
-// @Success 200 {object} LoginResponse
+// @Success 200 {object} auth.LoginResponse
// @Failure 400 {object} shared.ApiBody "Bad Request"
// @Failure 500 {object} shared.ApiBody "Internal Error"
// @Router /login [post]
@@ -63,7 +63,7 @@ func Login(ctx *gin.Context) {
// @Tags framework/NewPassword
// @Accept application/json
// @Param newpassword body auth.NewPasswordRequest true "json"
-// @Success 200 {object} shared.ApiBody
+// @Success 200 {object} auth.LoginResponse
// @Failure 400 {object} shared.ApiBody "Bad Request"
// @Failure 500 {object} shared.ApiBody "Internal Error"
// @Router /password [post]
@@ -81,3 +81,27 @@ func NewPassword(ctx *gin.Context) {
}
shared.ApiOutputSuccess(ctx, res, http.StatusOK)
}
+
+// @Summary post RefreshToken
+// @Description post RefreshToken
+// @Tags framework/RefreshToken
+// @Accept application/json
+// @Param refreshtoken body auth.RefreshTokenRequest true "json"
+// @Success 200 {object} auth.LoginResponse
+// @Failure 400 {object} shared.ApiBody "Bad Request"
+// @Failure 500 {object} shared.ApiBody "Internal Error"
+// @Router /password [post]
+func RefreshToken(ctx *gin.Context) {
+ req := &auth.RefreshTokenRequest{}
+ err := ctx.ShouldBind(req)
+ if err != nil {
+ shared.ApiOutputError(ctx, errors.BadInput.Wrap(err, shared.BadRequestBody))
+ return
+ }
+ res, err := auth.Provider.RefreshToken(req)
+ if err != nil {
+ shared.ApiOutputError(ctx, errors.BadInput.Wrap(err, "failed to refresh token"))
+ return
+ }
+ shared.ApiOutputSuccess(ctx, res, http.StatusOK)
+}
diff --git a/backend/server/services/auth/auth.go b/backend/server/services/auth/auth.go
index 742560dc6..fe839e2d3 100644
--- a/backend/server/services/auth/auth.go
+++ b/backend/server/services/auth/auth.go
@@ -55,10 +55,15 @@ type NewPasswordRequest struct {
Session string `json:"session"`
}
+type RefreshTokenRequest struct {
+ RefreshToken string `json:"refreshToken"`
+}
+
// auth provider interface
type AuthProvider interface {
SignIn(*LoginRequest) (*LoginResponse, errors.Error)
NewPassword(*NewPasswordRequest) (*LoginResponse, errors.Error)
+ RefreshToken(*RefreshTokenRequest) (*LoginResponse, errors.Error)
// ChangePassword(ctx *gin.Context, oldPassword, newPassword string) errors.Error
CheckAuth(token string) (*jwt.Token, errors.Error)
}
diff --git a/backend/server/services/auth/cognito.go b/backend/server/services/auth/cognito.go
index ac9f24d7e..37c13f6ae 100644
--- a/backend/server/services/auth/cognito.go
+++ b/backend/server/services/auth/cognito.go
@@ -113,11 +113,14 @@ func (cgt *AwsCognitoProvider) SignIn(loginReq *LoginRequest) (*LoginResponse, e
}
// Call Cognito to get auth tokens
+ return cgt.initiateAuth(input)
+}
+
+func (cgt *AwsCognitoProvider) initiateAuth(input *cognitoidentityprovider.InitiateAuthInput) (*LoginResponse, errors.Error) {
response, err := cgt.client.InitiateAuth(input)
if err != nil {
return nil, errors.BadInput.New(err.Error())
}
-
loginRes := &LoginResponse{
ChallengeName: response.ChallengeName,
ChallengeParameters: response.ChallengeParameters,
@@ -132,7 +135,6 @@ func (cgt *AwsCognitoProvider) SignIn(loginReq *LoginRequest) (*LoginResponse, e
TokenType: response.AuthenticationResult.TokenType,
}
}
-
return loginRes, nil
}
@@ -164,7 +166,15 @@ func (cgt *AwsCognitoProvider) CheckAuth(tokenString string) (*jwt.Token, errors
return nil, fmt.Errorf("Public key not found")
})
- // Check if the token is invalid
+ if err != nil {
+ if ve, ok := err.(*jwt.ValidationError); ok {
+ if ve.Errors == jwt.ValidationErrorExpired {
+ return nil, errors.Forbidden.New("Token expired")
+ }
+ }
+ }
+
+ // Check if the token is valid
if err != nil || !token.Valid {
cgt.logger.Error(err, "Invalid token")
return nil, errors.Unauthorized.New("Invalid token")
@@ -175,11 +185,11 @@ func (cgt *AwsCognitoProvider) CheckAuth(tokenString string) (*jwt.Token, errors
if actualClaims, ok := token.Claims.(jwt.MapClaims); ok {
for key, expected := range cgt.expectClaims {
if expected != actualClaims[key] {
- return nil, errors.Unauthorized.New("Invalid token")
+ return nil, errors.Unauthorized.New("Invalid token: expected claims do not match")
}
}
} else {
- return nil, errors.Unauthorized.New("Invalid token")
+ return nil, errors.Unauthorized.New("Invalid token: expected claims do not match")
}
}
@@ -236,6 +246,18 @@ func (cgt *AwsCognitoProvider) NewPassword(newPasswordReq *NewPasswordRequest) (
return loginRes, nil
}
+func (cgt *AwsCognitoProvider) RefreshToken(req *RefreshTokenRequest) (*LoginResponse, errors.Error) {
+ // Create the input for InitiateAuth
+ input := &cognitoidentityprovider.InitiateAuthInput{
+ AuthFlow: aws.String(cognitoidentityprovider.AuthFlowTypeRefreshTokenAuth),
+ ClientId: cgt.clientId,
+ AuthParameters: map[string]*string{
+ "REFRESH_TOKEN": aws.String(req.RefreshToken),
+ },
+ }
+ return cgt.initiateAuth(input)
+}
+
// func (cgt *AwsCognitorProvider) ChangePassword(ctx *gin.Context, oldPassword, newPassword string) errors.Error {
// token := ctx.GetString(("token"))
// if token == "" {
diff --git a/config-ui/src/pages/login/login.tsx b/config-ui/src/pages/login/login.tsx
index d130797c8..69105f317 100644
--- a/config-ui/src/pages/login/login.tsx
+++ b/config-ui/src/pages/login/login.tsx
@@ -68,6 +68,7 @@ export const LoginPage = () => {
setSession(res.session);
} else {
localStorage.setItem('accessToken', res.authenticationResult.accessToken);
+ localStorage.setItem('refreshToken', res.authenticationResult.refreshToken);
document.cookie = 'access_token=' + res.authenticationResult.accessToken + '; path=/';
setUsername('');
setPassword('');
diff --git a/config-ui/src/utils/request.ts b/config-ui/src/utils/request.ts
index db676a21f..b345293e1 100644
--- a/config-ui/src/utils/request.ts
+++ b/config-ui/src/utils/request.ts
@@ -27,6 +27,8 @@ const instance = axios.create({
baseURL: DEVLAKE_ENDPOINT,
});
+var refreshingToken: Promise<any> | null = null;
+
instance.interceptors.response.use(
(response) => response,
(error) => {
@@ -37,6 +39,39 @@ instance.interceptors.response.use(
history.push('/login');
}
+ if (status === 403) {
+ var refreshToken = localStorage.getItem('refreshToken');
+ if (refreshToken) {
+ refreshingToken =
+ refreshingToken ||
+ request('/login/refreshtoken', {
+ method: 'POST',
+ data: {
+ refreshToken: refreshToken,
+ },
+ }).then(
+ (resp) => {
+ localStorage.setItem('accessToken', resp.authenticationResult.accessToken);
+ refreshingToken = null;
+ return resp;
+ },
+ (err) => {
+ refreshingToken = null;
+ toast.error('Please login first');
+ history.push('/login');
+ return Promise.reject(err);
+ },
+ );
+ return refreshingToken.then(() => {
+ const originalRequest = error.config;
+ originalRequest._retry = true;
+ return Promise.resolve(request(originalRequest.url, originalRequest));
+ });
+ } else {
+ history.push('/login');
+ }
+ }
+
if (status === 428) {
history.push('/db-migrate');
}