feat: add long-lived api tokens (#1085)
Co-authored-by: kolaente <k@knt.li> Reviewed-on: https://kolaente.dev/vikunja/api/pulls/1085 Co-authored-by: konrad <k@knt.li> Co-committed-by: konrad <k@knt.li>
This commit is contained in:
parent
2598550e49
commit
1322cb16d7
11 changed files with 51 additions and 8 deletions
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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("")
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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()))
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue