github.com/qri-io/qri@v0.10.1-0.20220104210721-c771715036cb/auth/token/token_provider.go (about) 1 package token 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 "time" 8 9 "github.com/qri-io/qri/auth/key" 10 "github.com/qri-io/qri/profile" 11 ) 12 13 const ( 14 // RTCode signals the token response type is 'code' 15 RTCode ResponseType = "code" 16 // RTToken signals the token response type is 'token' 17 RTToken ResponseType = "token" 18 19 // AccessTokenTTL is the lifespan of an access token 20 AccessTokenTTL = time.Hour * 2 21 // RefreshTokenTTL is the lifespan of a refresh token 22 RefreshTokenTTL = time.Hour * 24 * 30 23 // AccessCodeTTL is the lifespan of an access code 24 AccessCodeTTL = time.Minute * 2 25 ) 26 27 var ( 28 // ErrInvalidRequest is returned on any parse or void output error 29 ErrInvalidRequest = fmt.Errorf("invalid request") 30 // ErrInvalidCredentials signals a bad username/password/key error 31 ErrInvalidCredentials = fmt.Errorf("invalid user credentials") 32 // ErrNotFound is returned when no matching results exist for the provided credentials 33 ErrNotFound = fmt.Errorf("user not found") 34 // ErrServerError is returned on unexpected errors 35 ErrServerError = fmt.Errorf("server error") 36 // ErrInvalidAuthorizeCode is returned on parsing an invalid authorization code 37 ErrInvalidAuthorizeCode = fmt.Errorf("invalid authorize code") 38 // ErrInvalidAccessToken is returned on parsing an invalid access token 39 ErrInvalidAccessToken = fmt.Errorf("invalid access token") 40 // ErrCodeExpired is returned for expired authorization codes 41 ErrCodeExpired = fmt.Errorf("code expired") 42 // ErrTokenExpired is returned for expired tokens 43 ErrTokenExpired = fmt.Errorf("token expired") 44 // ErrInvalidRefreshToken is returned on parsing invalid refresh tokens 45 ErrInvalidRefreshToken = fmt.Errorf("invalid refresh token") 46 ) 47 48 // Provider is a service that generates access & refresh tokens 49 type Provider interface { 50 // Token handles the auth token flow 51 Token(ctx context.Context, req *Request) (*Response, error) 52 } 53 54 // Request is a wrapper for incoming token requests 55 type Request struct { 56 GrantType GrantType `json:"grant_type"` 57 Code string `json:"code"` 58 Username string `json:"username"` 59 Password string `json:"password"` 60 RefreshToken string `json:"refresh_token"` 61 RedirectURI string `json:"redirect_uri"` 62 } 63 64 // Response wraps the token response object 65 type Response struct { 66 AccessToken string `json:"access_token"` 67 TokenType string `json:"token_type"` 68 ExpiresIn int64 `json:"expires_in"` 69 RefreshToken string `json:"refresh_token,omitempty"` 70 } 71 72 // ResponseType the type of authorization request 73 type ResponseType string 74 75 func (rt ResponseType) String() string { 76 return string(rt) 77 } 78 79 // GrantType authorization model 80 type GrantType string 81 82 // define authorization model 83 const ( 84 AuthorizationCode GrantType = "authorization_code" 85 PasswordCredentials GrantType = "password" 86 ClientCredentials GrantType = "client_credentials" 87 Refreshing GrantType = "refresh_token" 88 Implicit GrantType = "__implicit" 89 ) 90 91 func (gt GrantType) String() string { 92 if gt == AuthorizationCode || 93 gt == PasswordCredentials || 94 gt == ClientCredentials || 95 gt == Refreshing { 96 return string(gt) 97 } 98 return "" 99 } 100 101 // ClientType is used to enumerate the user types to distingish them later from the token 102 type ClientType string 103 104 const ( 105 // UserClient represents a human user that's authenticated with his own credentials 106 UserClient ClientType = "user" 107 // NodeClient represents a machine client that's authenticated with api client credentials 108 NodeClient ClientType = "node" 109 ) 110 111 func (ct ClientType) String() string { 112 return string(ct) 113 } 114 115 // LocalProvider implements the Provider interface and 116 // provides mechanics for generating tokens for a selected profile 117 type LocalProvider struct { 118 profiles profile.Store 119 keys key.Store 120 } 121 122 // NewProvider instantiates a new LocalProvider 123 func NewProvider(p profile.Store, k key.Store) (*LocalProvider, error) { 124 return &LocalProvider{ 125 profiles: p, 126 keys: k, 127 }, nil 128 } 129 130 // compile-time assertion that LocalProvider is a token.Provider 131 var _ Provider = (*LocalProvider)(nil) 132 133 // Token handles the OAuth token flow 134 func (p *LocalProvider) Token(ctx context.Context, req *Request) (*Response, error) { 135 log.Debugf("token.Provider got request: %+v", req) 136 resp := &Response{TokenType: "jwt", ExpiresIn: int64(AccessTokenTTL.Seconds())} 137 switch req.GrantType { 138 case PasswordCredentials: 139 if req.Username == "" { 140 return nil, ErrInvalidCredentials 141 } 142 // TODO(arqu): this only selects the first returned profile for a given peername. 143 // ideally we would use the profile.ID to fetch the exact profile 144 // or otherwise validate the signatures 145 pros, err := p.profiles.ProfilesForUsername(ctx, req.Username) 146 if err != nil { 147 log.Debugf("token.Provider failed to fetch profiles: %q", err.Error()) 148 return nil, ErrInvalidRequest 149 } 150 if len(pros) == 0 { 151 log.Debugf("token.Provider no matching profiles found") 152 return nil, ErrNotFound 153 } 154 if len(pros) > 1 { 155 log.Infof("token.Provider found multiple profiles for the given username - selected the first one") 156 } 157 pro := pros[0] 158 if pro.PrivKey == nil { 159 log.Debugf("token.Provider private key is nil") 160 return nil, ErrInvalidCredentials 161 } 162 accessToken, err := NewPrivKeyAuthToken(pro.PrivKey, pro.ID.Encode(), AccessTokenTTL) 163 if err != nil { 164 log.Debugf("token.Provider failed to generate access token: %q", err.Error()) 165 return nil, ErrInvalidRequest 166 } 167 refreshToken, err := NewPrivKeyAuthToken(pro.PrivKey, pro.ID.Encode(), RefreshTokenTTL) 168 if err != nil { 169 log.Debugf("token.Provider failed to generate refresh token: %q", err.Error()) 170 return nil, ErrInvalidRequest 171 } 172 resp.AccessToken = accessToken 173 resp.RefreshToken = refreshToken 174 case Refreshing: 175 if req.RefreshToken == "" { 176 return nil, ErrInvalidRequest 177 } 178 tok, err := ParseAuthToken(ctx, req.RefreshToken, p.keys) 179 if err != nil { 180 log.Debugf("token.Provider error parsing refresh token: %q", err.Error()) 181 return nil, ErrInvalidRequest 182 } 183 184 if claims, ok := tok.Claims.(*Claims); ok { 185 pid, err := profile.IDB58Decode(claims.Subject) 186 if err != nil { 187 log.Debugf("token.Provider failed to parse profileID") 188 return nil, ErrInvalidRequest 189 } 190 pro, err := p.profiles.GetProfile(ctx, pid) 191 if errors.Is(err, profile.ErrNotFound) { 192 log.Debugf("token.Provider profile not found") 193 return nil, ErrNotFound 194 } 195 accessToken, err := NewPrivKeyAuthToken(pro.PrivKey, pro.ID.Encode(), AccessTokenTTL) 196 if err != nil { 197 log.Debugf("token.Provider failed to generate access token: %q", err.Error()) 198 return nil, ErrInvalidRequest 199 } 200 resp.AccessToken = accessToken 201 } 202 default: 203 return nil, ErrInvalidRequest 204 } 205 return resp, nil 206 }