diff --git a/config.yml.sample b/config.yml.sample index b36fa23bd..607dc55ef 100644 --- a/config.yml.sample +++ b/config.yml.sample @@ -6,6 +6,10 @@ service: # The duration of the issed JWT tokens in seconds. # The default is 259200 seconds (3 Days). jwtttl: 259200 + # The duration of the "remember me" time in seconds. When the login request is made with + # the long param set, the token returned will be valid for this period. + # The default is 2592000 seconds (30 Days). + jwtttllong: 2592000 # The interface on which to run the webserver interface: ":3456" # Path to Unix socket. If set, it will be created and used instead of tcp diff --git a/docs/content/doc/setup/config.md b/docs/content/doc/setup/config.md index f5ad3f81e..e6e17f958 100644 --- a/docs/content/doc/setup/config.md +++ b/docs/content/doc/setup/config.md @@ -91,6 +91,19 @@ Full path: `service.jwtttl` Environment path: `VIKUNJA_SERVICE_JWTTTL` +### jwtttllong + +The duration of the "remember me" time in seconds. When the login request is made with +the long param set, the token returned will be valid for this period. +The default is 2592000 seconds (30 Days). + +Default: `2592000` + +Full path: `service.jwtttllong` + +Environment path: `VIKUNJA_SERVICE_JWTTTLLONG` + + ### interface The interface on which to run the webserver diff --git a/pkg/config/config.go b/pkg/config/config.go index d0ec87c86..61039865f 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -38,6 +38,7 @@ const ( // #nosec ServiceJWTSecret Key = `service.JWTSecret` ServiceJWTTTL Key = `service.jwtttl` + ServiceJWTTTLLong Key = `service.jwtttllong` ServiceInterface Key = `service.interface` ServiceUnixSocket Key = `service.unixsocket` ServiceUnixSocketMode Key = `service.unixsocketmode` @@ -227,7 +228,8 @@ func InitDefaultConfig() { // Service ServiceJWTSecret.setDefault(random) - ServiceJWTTTL.setDefault(259200) + ServiceJWTTTL.setDefault(259200) // 72 hours + ServiceJWTTTLLong.setDefault(2592000) // 30 days ServiceInterface.setDefault(":3456") ServiceUnixSocket.setDefault("") ServiceFrontendurl.setDefault("") diff --git a/pkg/integrations/integrations.go b/pkg/integrations/integrations.go index 51b759f10..77f3a3ac2 100644 --- a/pkg/integrations/integrations.go +++ b/pkg/integrations/integrations.go @@ -119,7 +119,7 @@ func newTestRequest(t *testing.T, method string, handler func(ctx echo.Context) func addUserTokenToContext(t *testing.T, user *user.User, c echo.Context) { // Get the token as a string - token, err := auth.NewUserJWTAuthtoken(user) + token, err := auth.NewUserJWTAuthtoken(user, false) assert.NoError(t, err) // We send the string token through the parsing function to get a valid jwt.Token tken, err := jwt.Parse(token, func(t *jwt.Token) (interface{}, error) { diff --git a/pkg/modules/auth/auth.go b/pkg/modules/auth/auth.go index abe5d355f..54c6ba721 100644 --- a/pkg/modules/auth/auth.go +++ b/pkg/modules/auth/auth.go @@ -42,8 +42,8 @@ type Token struct { } // NewUserAuthTokenResponse creates a new user auth token response from a user object. -func NewUserAuthTokenResponse(u *user.User, c echo.Context) error { - t, err := NewUserJWTAuthtoken(u) +func NewUserAuthTokenResponse(u *user.User, c echo.Context, long bool) error { + t, err := NewUserJWTAuthtoken(u, long) if err != nil { return err } @@ -52,10 +52,13 @@ func NewUserAuthTokenResponse(u *user.User, c echo.Context) error { } // NewUserJWTAuthtoken generates and signes a new jwt token for a user. This is a global function to be able to call it from integration tests. -func NewUserJWTAuthtoken(u *user.User) (token string, err error) { +func NewUserJWTAuthtoken(u *user.User, long bool) (token string, err error) { t := jwt.New(jwt.SigningMethodHS256) var ttl = time.Duration(config.ServiceJWTTTL.GetInt64()) + if long { + ttl = time.Duration(config.ServiceJWTTTLLong.GetInt64()) + } var exp = time.Now().Add(time.Second * ttl).Unix() // Set claims @@ -68,6 +71,7 @@ func NewUserJWTAuthtoken(u *user.User) (token string, err error) { claims["name"] = u.Name claims["emailRemindersEnabled"] = u.EmailRemindersEnabled claims["isLocalUser"] = u.Issuer == user.IssuerLocal + claims["long"] = long // Generate encoded token and send it as response. return t.SignedString([]byte(config.ServiceJWTSecret.GetString())) diff --git a/pkg/modules/auth/openid/openid.go b/pkg/modules/auth/openid/openid.go index 5b54d42e2..4cc6e9f55 100644 --- a/pkg/modules/auth/openid/openid.go +++ b/pkg/modules/auth/openid/openid.go @@ -198,7 +198,7 @@ func HandleCallback(c echo.Context) error { } // Create token - return auth.NewUserAuthTokenResponse(u, c) + return auth.NewUserAuthTokenResponse(u, c, false) } func getOrCreateUser(s *xorm.Session, cl *claims, issuer, subject string) (u *user.User, err error) { diff --git a/pkg/routes/api/v1/login.go b/pkg/routes/api/v1/login.go index f606769df..59dd11498 100644 --- a/pkg/routes/api/v1/login.go +++ b/pkg/routes/api/v1/login.go @@ -102,7 +102,7 @@ func Login(c echo.Context) error { } // Create token - return auth.NewUserAuthTokenResponse(user, c) + return auth.NewUserAuthTokenResponse(user, c, u.LongToken) } // RenewToken gives a new token to every user with a valid token @@ -156,6 +156,12 @@ func RenewToken(c echo.Context) (err error) { return handler.HandleHTTPError(err, c) } + var long bool + lng, has := claims["long"] + if has { + long = lng.(bool) + } + // Create token - return auth.NewUserAuthTokenResponse(user, c) + return auth.NewUserAuthTokenResponse(user, c, long) } diff --git a/pkg/swagger/docs.go b/pkg/swagger/docs.go index 35eb0dad3..ca64c34a2 100644 --- a/pkg/swagger/docs.go +++ b/pkg/swagger/docs.go @@ -8847,6 +8847,10 @@ var doc = `{ "user.Login": { "type": "object", "properties": { + "long_token": { + "description": "If true, the token returned will be valid a lot longer than default. Useful for \"remember me\" style logins.", + "type": "boolean" + }, "password": { "description": "The password for the user.", "type": "string" diff --git a/pkg/swagger/swagger.json b/pkg/swagger/swagger.json index fde0490ec..2e5d604a3 100644 --- a/pkg/swagger/swagger.json +++ b/pkg/swagger/swagger.json @@ -8831,6 +8831,10 @@ "user.Login": { "type": "object", "properties": { + "long_token": { + "description": "If true, the token returned will be valid a lot longer than default. Useful for \"remember me\" style logins.", + "type": "boolean" + }, "password": { "description": "The password for the user.", "type": "string" diff --git a/pkg/swagger/swagger.yaml b/pkg/swagger/swagger.yaml index e5d3eb633..5f72c4ce6 100644 --- a/pkg/swagger/swagger.yaml +++ b/pkg/swagger/swagger.yaml @@ -1157,6 +1157,10 @@ definitions: type: object user.Login: properties: + long_token: + description: If true, the token returned will be valid a lot longer than default. + Useful for "remember me" style logins. + type: boolean password: description: The password for the user. type: string diff --git a/pkg/user/user.go b/pkg/user/user.go index fb7cff664..1a7c31bfb 100644 --- a/pkg/user/user.go +++ b/pkg/user/user.go @@ -44,6 +44,8 @@ type Login struct { Password string `json:"password"` // The totp passcode of a user. Only needs to be provided when enabled. TOTPPasscode string `json:"totp_passcode"` + // If true, the token returned will be valid a lot longer than default. Useful for "remember me" style logins. + LongToken bool `json:"long_token"` } type Status int