mirror of
https://github.com/kataras/iris.git
synced 2025-01-24 03:01:03 +01:00
1864f99145
TODO: Write the new e-book JWT section and the HISTORY entry of the chnages and add a simple example on site docs
188 lines
4.4 KiB
Go
188 lines
4.4 KiB
Go
package jwt
|
|
|
|
import (
|
|
"time"
|
|
|
|
"github.com/kataras/iris/v12/context"
|
|
)
|
|
|
|
// User a common User structure for JWT.
|
|
// However, we're not limited to that one;
|
|
// any Go structure can be generated as a JWT token.
|
|
//
|
|
// Look `NewUser` and `VerifyUser` JWT middleware's methods.
|
|
// Use its `GetToken` method to generate the token when
|
|
// the User structure is set.
|
|
type User struct {
|
|
Claims
|
|
// Note: we could use a map too as the Token is generated when GetToken is called.
|
|
*context.SimpleUser
|
|
|
|
j *JWT
|
|
}
|
|
|
|
var (
|
|
_ context.FeaturedUser = (*User)(nil)
|
|
_ TokenSetter = (*User)(nil)
|
|
_ ContextValidator = (*User)(nil)
|
|
)
|
|
|
|
// UserOption sets optional fields for a new User
|
|
// See `NewUser` instance function.
|
|
type UserOption func(*User)
|
|
|
|
// Username sets the Username and the JWT Claim's Subject
|
|
// to the given "username".
|
|
func Username(username string) UserOption {
|
|
return func(u *User) {
|
|
u.Username = username
|
|
u.Claims.Subject = username
|
|
u.Features = append(u.Features, context.UsernameFeature)
|
|
}
|
|
}
|
|
|
|
// Email sets the Email field for the User field.
|
|
func Email(email string) UserOption {
|
|
return func(u *User) {
|
|
u.Email = email
|
|
u.Features = append(u.Features, context.EmailFeature)
|
|
}
|
|
}
|
|
|
|
// Roles upserts to the User's Roles field.
|
|
func Roles(roles ...string) UserOption {
|
|
return func(u *User) {
|
|
u.Roles = roles
|
|
u.Features = append(u.Features, context.RolesFeature)
|
|
}
|
|
}
|
|
|
|
// MaxAge sets claims expiration and the AuthorizedAt User field.
|
|
func MaxAge(maxAge time.Duration) UserOption {
|
|
return func(u *User) {
|
|
now := time.Now()
|
|
u.Claims.Expiry = NewNumericDate(now.Add(maxAge))
|
|
u.Claims.IssuedAt = NewNumericDate(now)
|
|
u.AuthorizedAt = now
|
|
|
|
u.Features = append(u.Features, context.AuthorizedAtFeature)
|
|
}
|
|
}
|
|
|
|
// Fields copies the "fields" to the user's Fields field.
|
|
// This can be used to set custom fields to the User instance.
|
|
func Fields(fields context.Map) UserOption {
|
|
return func(u *User) {
|
|
if len(fields) == 0 {
|
|
return
|
|
}
|
|
|
|
if u.Fields == nil {
|
|
u.Fields = make(context.Map, len(fields))
|
|
}
|
|
|
|
for k, v := range fields {
|
|
u.Fields[k] = v
|
|
}
|
|
|
|
u.Features = append(u.Features, context.FieldsFeature)
|
|
}
|
|
}
|
|
|
|
// SetToken is called automaticaly on VerifyUser/VerifyObject.
|
|
// It sets the extracted from request, and verified from server raw token.
|
|
func (u *User) SetToken(token string) {
|
|
u.Token = token
|
|
}
|
|
|
|
// GetToken overrides the SimpleUser's Token
|
|
// and returns the jwt generated token, among with
|
|
// a generator error, if any.
|
|
func (u *User) GetToken() (string, error) {
|
|
if u.Token != "" {
|
|
return u.Token, nil
|
|
}
|
|
|
|
if u.j != nil { // it's always not nil.
|
|
if u.j.MaxAge > 0 {
|
|
// if the MaxAge option was not manually set, resolve it from the JWT instance.
|
|
MaxAge(u.j.MaxAge)(u)
|
|
}
|
|
|
|
// we could generate a token here
|
|
// but let's do it on GetToken
|
|
// as the user fields may change
|
|
// by the caller manually until the token
|
|
// sent to the client.
|
|
tok, err := u.j.Token(u)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
u.Token = tok
|
|
}
|
|
|
|
if u.Token == "" {
|
|
return "", ErrMissing
|
|
}
|
|
|
|
return u.Token, nil
|
|
}
|
|
|
|
// Validate validates the current user's claims against
|
|
// the request. It's called automatically by the JWT instance.
|
|
func (u *User) Validate(ctx *context.Context, claims Claims, e Expected) error {
|
|
err := u.Claims.ValidateWithLeeway(e, 0)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if u.SimpleUser.Authorization != "IRIS_JWT_USER" {
|
|
return ErrInvalidKey
|
|
}
|
|
|
|
// We could add specific User Expectations (new struct and accept an interface{}),
|
|
// but for the sake of code simplicity we don't, unless is requested, as the caller
|
|
// can validate specific fields by its own at the next step.
|
|
return nil
|
|
}
|
|
|
|
// UnmarshalJSON implements the json unmarshaler interface.
|
|
func (u *User) UnmarshalJSON(data []byte) error {
|
|
err := Unmarshal(data, &u.Claims)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
simpleUser := new(context.SimpleUser)
|
|
err = Unmarshal(data, simpleUser)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
u.SimpleUser = simpleUser
|
|
return nil
|
|
}
|
|
|
|
// MarshalJSON implements the json marshaler interface.
|
|
func (u *User) MarshalJSON() ([]byte, error) {
|
|
claimsB, err := Marshal(u.Claims)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
userB, err := Marshal(u.SimpleUser)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if len(userB) == 0 {
|
|
return claimsB, nil
|
|
}
|
|
|
|
claimsB = claimsB[0 : len(claimsB)-1] // remove last '}'
|
|
userB = userB[1:] // remove first '{'
|
|
|
|
raw := append(claimsB, ',')
|
|
raw = append(raw, userB...)
|
|
return raw, nil
|
|
}
|