auth
2014-09-01
Parent:1aa3a85ff853
auth/access.go.old
Rough out tokens and begin the memstore. Rough out the Token type for working with OAuth2 access and refresh tokens. Rough out the TokenStore interface that dictates how Tokens will be stored and retrieved. Write tests for the successful (in the working-as-intended sense) calls to TokenStore. Begin a Memstore type that stores data in memory. Implement the TokenStore interface for Memstore.
| paddy@23 | 1 package auth |
| paddy@23 | 2 |
| paddy@23 | 3 import ( |
| paddy@23 | 4 "net/http" |
| paddy@23 | 5 "net/url" |
| paddy@23 | 6 "time" |
| paddy@23 | 7 |
| paddy@23 | 8 "strconv" |
| paddy@23 | 9 "secondbit.org/uuid" |
| paddy@23 | 10 ) |
| paddy@23 | 11 |
| paddy@23 | 12 // GrantType is the type for OAuth param `grant_type` |
| paddy@23 | 13 type GrantType string |
| paddy@23 | 14 |
| paddy@23 | 15 const ( |
| paddy@23 | 16 AuthorizationCodeGrant GrantType = "authorization_code" |
| paddy@23 | 17 RefreshTokenGrant = "refresh_token" |
| paddy@23 | 18 PasswordGrant = "password" |
| paddy@23 | 19 ClientCredentialsGrant = "client_credentials" |
| paddy@23 | 20 ) |
| paddy@23 | 21 |
| paddy@23 | 22 // AccessData represents an access grant (tokens, expiration, client, etc) |
| paddy@23 | 23 type AccessData struct { |
| paddy@23 | 24 PreviousAuthorizeData *AuthorizeData `json:"-"` |
| paddy@23 | 25 PreviousAccessData *AccessData `json:"-"` // previous access data, when refreshing |
| paddy@23 | 26 AccessToken string `json:"access_token"` |
| paddy@23 | 27 RefreshToken string `json:"refresh_token,omitempty"` |
| paddy@23 | 28 ExpiresIn int32 `json:"expires_in"` |
| paddy@23 | 29 CreatedAt time.Time `json:"-"` |
| paddy@23 | 30 TokenType string `json:"token_type"` |
| paddy@23 | 31 Scope string `json:"scope,omitempty"` |
| paddy@23 | 32 ProfileID uuid.ID `json:"-"` |
| paddy@23 | 33 AuthRequest `json:"-"` |
| paddy@23 | 34 } |
| paddy@23 | 35 |
| paddy@23 | 36 // IsExpired returns true if access expired |
| paddy@23 | 37 func (d *AccessData) IsExpired() bool { |
| paddy@23 | 38 return d.CreatedAt.Add(time.Duration(d.ExpiresIn) * time.Second).Before(time.Now()) |
| paddy@23 | 39 } |
| paddy@23 | 40 |
| paddy@23 | 41 // ExpireAt returns the expiration date |
| paddy@23 | 42 func (d *AccessData) ExpireAt() time.Time { |
| paddy@23 | 43 return d.CreatedAt.Add(time.Duration(d.ExpiresIn) * time.Second) |
| paddy@23 | 44 } |
| paddy@23 | 45 |
| paddy@23 | 46 // HandleOAuth2AccessRequest is the http.HandlerFunc for handling access token requests. |
| paddy@23 | 47 func HandleOAuth2AccessRequest(w http.ResponseWriter, r *http.Request, ctx Context) { |
| paddy@23 | 48 // Only allow GET or POST |
| paddy@23 | 49 if r.Method != "POST" { |
| paddy@23 | 50 if r.Method != "GET" || !ctx.Config.AllowGetAccessRequest { |
| paddy@23 | 51 ctx.RenderJSONError(w, ErrorInvalidRequest, "Invalid request method.", ctx.Config.DocumentationDomain) |
| paddy@23 | 52 return |
| paddy@23 | 53 } |
| paddy@23 | 54 } |
| paddy@23 | 55 |
| paddy@23 | 56 grantType := GrantType(r.Form.Get("grant_type")) |
| paddy@23 | 57 if ctx.Config.AllowedAccessTypes.Exists(grantType) { |
| paddy@23 | 58 switch grantType { |
| paddy@23 | 59 case AuthorizationCodeGrant: |
| paddy@23 | 60 handleAuthorizationCodeRequest(w, r, ctx) |
| paddy@23 | 61 case RefreshTokenGrant: |
| paddy@23 | 62 handleRefreshTokenRequest(w, r, ctx) |
| paddy@23 | 63 case PasswordGrant: |
| paddy@23 | 64 handlePasswordRequest(w, r, ctx) |
| paddy@23 | 65 case ClientCredentialsGrant: |
| paddy@23 | 66 handleClientCredentialsRequest(w, r, ctx) |
| paddy@23 | 67 default: |
| paddy@23 | 68 ctx.RenderJSONError(w, ErrorUnsupportedGrantType, "Unsupported grant type.", ctx.Config.DocumentationDomain) |
| paddy@23 | 69 return |
| paddy@23 | 70 } |
| paddy@23 | 71 } |
| paddy@23 | 72 } |
| paddy@23 | 73 |
| paddy@23 | 74 func handleAuthorizationCodeRequest(w http.ResponseWriter, r *http.Request, ctx Context) { |
| paddy@23 | 75 // get client authentication |
| paddy@23 | 76 auth, err := getClientAuth(r, ctx.Config.AllowClientSecretInParams) |
| paddy@23 | 77 if err != nil { |
| paddy@23 | 78 ctx.RenderJSONError(w, ErrorInvalidClient, "Invalid client auth.", ctx.Config.DocumentationDomain) |
| paddy@23 | 79 return |
| paddy@23 | 80 } |
| paddy@23 | 81 |
| paddy@23 | 82 code := r.Form.Get("code") |
| paddy@23 | 83 // "code" is required |
| paddy@23 | 84 if code == "" { |
| paddy@23 | 85 ctx.RenderJSONError(w, ErrorInvalidRequest, "Code must be supplied.", ctx.Config.DocumentationDomain) |
| paddy@23 | 86 return |
| paddy@23 | 87 } |
| paddy@23 | 88 |
| paddy@23 | 89 // must have a valid client |
| paddy@23 | 90 client, err := getClient(auth, ctx) |
| paddy@23 | 91 if err != nil { |
| paddy@23 | 92 if err == ClientNotFoundError || err == InvalidClientError { |
| paddy@23 | 93 ctx.RenderJSONError(w, ErrorInvalidClient, "Invalid client auth.", ctx.Config.DocumentationDomain) |
| paddy@23 | 94 return |
| paddy@23 | 95 } |
| paddy@23 | 96 ctx.RenderJSONError(w, ErrorServerError, "Internal server error.", ctx.Config.DocumentationDomain) |
| paddy@23 | 97 return |
| paddy@23 | 98 } |
| paddy@23 | 99 |
| paddy@23 | 100 // must be a valid authorization code |
| paddy@23 | 101 authData, err := ctx.Tokens.GetAuthorization(code) |
| paddy@23 | 102 if err != nil { |
| paddy@23 | 103 if err == AuthorizationNotFoundError { |
| paddy@23 | 104 ctx.RenderJSONError(w, ErrorInvalidGrant, "Invalid authorization.", ctx.Config.DocumentationDomain) |
| paddy@23 | 105 return |
| paddy@23 | 106 } |
| paddy@23 | 107 ctx.RenderJSONError(w, ErrorServerError, "Internal server error.", ctx.Config.DocumentationDomain) |
| paddy@23 | 108 return |
| paddy@23 | 109 } |
| paddy@23 | 110 if authData.RedirectURI == "" { |
| paddy@23 | 111 ctx.RenderJSONError(w, ErrorInvalidGrant, "Invalid redirect on grant.", ctx.Config.DocumentationDomain) |
| paddy@23 | 112 return |
| paddy@23 | 113 } |
| paddy@23 | 114 if authData.IsExpired() { |
| paddy@23 | 115 ctx.RenderJSONError(w, ErrorInvalidGrant, "Authorization is expired.", ctx.Config.DocumentationDomain) |
| paddy@23 | 116 return |
| paddy@23 | 117 } |
| paddy@23 | 118 |
| paddy@23 | 119 // code must be from the client |
| paddy@23 | 120 if !authData.Client.ID.Equal(client.ID) { |
| paddy@23 | 121 ctx.RenderJSONError(w, ErrorInvalidGrant, "Grant issued to another client.", ctx.Config.DocumentationDomain) |
| paddy@23 | 122 return |
| paddy@23 | 123 } |
| paddy@23 | 124 |
| paddy@23 | 125 // check redirect uri |
| paddy@23 | 126 redirectURI := r.Form.Get("redirect_uri") |
| paddy@23 | 127 if redirectURI == "" { |
| paddy@23 | 128 redirectURI = client.RedirectURI |
| paddy@23 | 129 } |
| paddy@23 | 130 if err = validateURI(client.RedirectURI, redirectURI); err != nil { |
| paddy@23 | 131 ctx.RenderJSONError(w, ErrorInvalidGrant, "Redirect URI doesn't match client.", ctx.Config.DocumentationDomain) |
| paddy@23 | 132 return |
| paddy@23 | 133 } |
| paddy@23 | 134 if authData.RedirectURI != redirectURI { |
| paddy@23 | 135 ctx.RenderJSONError(w, ErrorInvalidGrant, "Redirect URI doesn't match auth redirect.", ctx.Config.DocumentationDomain) |
| paddy@23 | 136 return |
| paddy@23 | 137 } |
| paddy@23 | 138 |
| paddy@23 | 139 data := AccessData{ |
| paddy@23 | 140 AuthRequest: AuthRequest{ |
| paddy@23 | 141 Client: client, |
| paddy@23 | 142 RedirectURI: redirectURI, |
| paddy@23 | 143 Scope: authData.Scope, |
| paddy@23 | 144 }, |
| paddy@23 | 145 Scope: authData.Scope, |
| paddy@23 | 146 PreviousAuthorizeData: &authData, |
| paddy@23 | 147 } |
| paddy@23 | 148 |
| paddy@23 | 149 err = fillTokens(&data, true, ctx) |
| paddy@23 | 150 if err != nil { |
| paddy@23 | 151 ctx.RenderJSONError(w, ErrorServerError, "Internal server error.", ctx.Config.DocumentationDomain) |
| paddy@23 | 152 return |
| paddy@23 | 153 } |
| paddy@23 | 154 ctx.RenderJSONToken(w, data) |
| paddy@23 | 155 } |
| paddy@23 | 156 |
| paddy@23 | 157 func handleRefreshTokenRequest(w http.ResponseWriter, r *http.Request, ctx Context) { |
| paddy@23 | 158 // get client authentication |
| paddy@23 | 159 auth, err := getClientAuth(r, ctx.Config.AllowClientSecretInParams) |
| paddy@23 | 160 |
| paddy@23 | 161 if err != nil { |
| paddy@23 | 162 ctx.RenderJSONError(w, ErrorInvalidClient, "Invalid client auth.", ctx.Config.DocumentationDomain) |
| paddy@23 | 163 return |
| paddy@23 | 164 } |
| paddy@23 | 165 |
| paddy@23 | 166 code := r.Form.Get("refresh_token") |
| paddy@23 | 167 |
| paddy@23 | 168 // "refresh_token" is required |
| paddy@23 | 169 if code == "" { |
| paddy@23 | 170 ctx.RenderJSONError(w, ErrorInvalidRequest, "Missing refresh token.", ctx.Config.DocumentationDomain) |
| paddy@23 | 171 return |
| paddy@23 | 172 } |
| paddy@23 | 173 |
| paddy@23 | 174 // must have a valid client |
| paddy@23 | 175 client, err := getClient(auth, ctx) |
| paddy@23 | 176 if err != nil { |
| paddy@23 | 177 if err == ClientNotFoundError || err == InvalidClientError { |
| paddy@23 | 178 ctx.RenderJSONError(w, ErrorInvalidClient, "Invalid client auth.", ctx.Config.DocumentationDomain) |
| paddy@23 | 179 return |
| paddy@23 | 180 } |
| paddy@23 | 181 ctx.RenderJSONError(w, ErrorServerError, "Internal server error.", ctx.Config.DocumentationDomain) |
| paddy@23 | 182 return |
| paddy@23 | 183 } |
| paddy@23 | 184 |
| paddy@23 | 185 // must be a valid refresh code |
| paddy@23 | 186 refreshData, err := ctx.Tokens.GetRefresh(code) |
| paddy@23 | 187 if err != nil { |
| paddy@23 | 188 if err == TokenNotFoundError { |
| paddy@23 | 189 ctx.RenderJSONError(w, ErrorInvalidGrant, "Refresh token not valid.", ctx.Config.DocumentationDomain) |
| paddy@23 | 190 return |
| paddy@23 | 191 } |
| paddy@23 | 192 ctx.RenderJSONError(w, ErrorServerError, "Internal server error.", ctx.Config.DocumentationDomain) |
| paddy@23 | 193 return |
| paddy@23 | 194 } |
| paddy@23 | 195 |
| paddy@23 | 196 // client must be the same as the previous token |
| paddy@23 | 197 if !refreshData.Client.ID.Equal(client.ID) { |
| paddy@23 | 198 ctx.RenderJSONError(w, ErrorInvalidGrant, "Refresh token issued to another client.", ctx.Config.DocumentationDomain) |
| paddy@23 | 199 return |
| paddy@23 | 200 } |
| paddy@23 | 201 |
| paddy@23 | 202 scope := r.Form.Get("scope") |
| paddy@23 | 203 if scope == "" { |
| paddy@23 | 204 scope = refreshData.Scope |
| paddy@23 | 205 } |
| paddy@23 | 206 |
| paddy@23 | 207 data := AccessData{ |
| paddy@23 | 208 AuthRequest: AuthRequest{ |
| paddy@23 | 209 Client: client, |
| paddy@23 | 210 Scope: scope, |
| paddy@23 | 211 }, |
| paddy@23 | 212 Scope: scope, |
| paddy@23 | 213 PreviousAccessData: &refreshData, |
| paddy@23 | 214 } |
| paddy@23 | 215 err = fillTokens(&data, true, ctx) |
| paddy@23 | 216 if err != nil { |
| paddy@23 | 217 ctx.RenderJSONError(w, ErrorServerError, "Internal server error.", ctx.Config.DocumentationDomain) |
| paddy@23 | 218 return |
| paddy@23 | 219 } |
| paddy@23 | 220 ctx.RenderJSONToken(w, data) |
| paddy@23 | 221 } |
| paddy@23 | 222 |
| paddy@23 | 223 func handlePasswordRequest(w http.ResponseWriter, r *http.Request, ctx Context) { |
| paddy@23 | 224 // get client authentication |
| paddy@23 | 225 auth, err := getClientAuth(r, ctx.Config.AllowClientSecretInParams) |
| paddy@23 | 226 if err != nil { |
| paddy@23 | 227 ctx.RenderJSONError(w, ErrorInvalidClient, "Invalid client auth.", ctx.Config.DocumentationDomain) |
| paddy@23 | 228 return |
| paddy@23 | 229 } |
| paddy@23 | 230 |
| paddy@23 | 231 username := r.Form.Get("username") |
| paddy@23 | 232 password := r.Form.Get("password") |
| paddy@23 | 233 scope := r.Form.Get("scope") |
| paddy@23 | 234 |
| paddy@23 | 235 // "username" and "password" is required |
| paddy@23 | 236 if username == "" || password == "" { |
| paddy@23 | 237 ctx.RenderJSONError(w, ErrorInvalidRequest, "Missing credentials.", ctx.Config.DocumentationDomain) |
| paddy@23 | 238 return |
| paddy@23 | 239 } |
| paddy@23 | 240 |
| paddy@23 | 241 // must have a valid client |
| paddy@23 | 242 client, err := getClient(auth, ctx) |
| paddy@23 | 243 if err != nil { |
| paddy@23 | 244 if err == ClientNotFoundError || err == InvalidClientError { |
| paddy@23 | 245 ctx.RenderJSONError(w, ErrorInvalidClient, "Invalid client auth.", ctx.Config.DocumentationDomain) |
| paddy@23 | 246 return |
| paddy@23 | 247 } |
| paddy@23 | 248 ctx.RenderJSONError(w, ErrorServerError, "Internal server error.", ctx.Config.DocumentationDomain) |
| paddy@23 | 249 return |
| paddy@23 | 250 } |
| paddy@23 | 251 |
| paddy@23 | 252 _, err = ctx.Profiles.GetProfile(username, password) |
| paddy@23 | 253 if err != nil { |
| paddy@23 | 254 if err == ErrProfileNotFound { |
| paddy@23 | 255 ctx.RenderJSONError(w, ErrorInvalidGrant, "Invalid credentials.", ctx.Config.DocumentationDomain) |
| paddy@23 | 256 return |
| paddy@23 | 257 } |
| paddy@23 | 258 ctx.RenderJSONError(w, ErrorServerError, "Internal server error.", ctx.Config.DocumentationDomain) |
| paddy@23 | 259 return |
| paddy@23 | 260 } |
| paddy@23 | 261 |
| paddy@23 | 262 data := AccessData{ |
| paddy@23 | 263 AuthRequest: AuthRequest{ |
| paddy@23 | 264 Client: client, |
| paddy@23 | 265 Scope: scope, |
| paddy@23 | 266 }, |
| paddy@23 | 267 Scope: scope, |
| paddy@23 | 268 } |
| paddy@23 | 269 |
| paddy@23 | 270 err = fillTokens(&data, true, ctx) |
| paddy@23 | 271 if err != nil { |
| paddy@23 | 272 ctx.RenderJSONError(w, ErrorServerError, "Internal server error.", ctx.Config.DocumentationDomain) |
| paddy@23 | 273 return |
| paddy@23 | 274 } |
| paddy@23 | 275 ctx.RenderJSONToken(w, data) |
| paddy@23 | 276 } |
| paddy@23 | 277 |
| paddy@23 | 278 func handleClientCredentialsRequest(w http.ResponseWriter, r *http.Request, ctx Context) { |
| paddy@23 | 279 // get client authentication |
| paddy@23 | 280 auth, err := getClientAuth(r, ctx.Config.AllowClientSecretInParams) |
| paddy@23 | 281 if err != nil { |
| paddy@23 | 282 ctx.RenderJSONError(w, ErrorInvalidClient, "Invalid client auth.", ctx.Config.DocumentationDomain) |
| paddy@23 | 283 return |
| paddy@23 | 284 } |
| paddy@23 | 285 |
| paddy@23 | 286 scope := r.Form.Get("scope") |
| paddy@23 | 287 |
| paddy@23 | 288 // must have a valid client |
| paddy@23 | 289 client, err := getClient(auth, ctx) |
| paddy@23 | 290 if err != nil { |
| paddy@23 | 291 if err == ClientNotFoundError || err == InvalidClientError { |
| paddy@23 | 292 ctx.RenderJSONError(w, ErrorInvalidClient, "Invalid client auth.", ctx.Config.DocumentationDomain) |
| paddy@23 | 293 return |
| paddy@23 | 294 } |
| paddy@23 | 295 ctx.RenderJSONError(w, ErrorServerError, "Internal server error.", ctx.Config.DocumentationDomain) |
| paddy@23 | 296 return |
| paddy@23 | 297 } |
| paddy@23 | 298 |
| paddy@23 | 299 data := AccessData{ |
| paddy@23 | 300 AuthRequest: AuthRequest{ |
| paddy@23 | 301 Client: client, |
| paddy@23 | 302 Scope: scope, |
| paddy@23 | 303 }, |
| paddy@23 | 304 Scope: scope, |
| paddy@23 | 305 } |
| paddy@23 | 306 |
| paddy@23 | 307 err = fillTokens(&data, true, ctx) |
| paddy@23 | 308 if err != nil { |
| paddy@23 | 309 ctx.RenderJSONError(w, ErrorServerError, "Internal server error.", ctx.Config.DocumentationDomain) |
| paddy@23 | 310 return |
| paddy@23 | 311 } |
| paddy@23 | 312 ctx.RenderJSONToken(w, data) |
| paddy@23 | 313 } |
| paddy@23 | 314 |
| paddy@23 | 315 func fillTokens(data *AccessData, includeRefresh bool, ctx Context) error { |
| paddy@23 | 316 var err error |
| paddy@23 | 317 |
| paddy@23 | 318 // generate access token |
| paddy@23 | 319 data.AccessToken = newToken() |
| paddy@23 | 320 if includeRefresh { |
| paddy@23 | 321 data.RefreshToken = newToken() |
| paddy@23 | 322 } |
| paddy@23 | 323 |
| paddy@23 | 324 // save access token |
| paddy@23 | 325 err = ctx.Tokens.SaveAccess(*data) |
| paddy@23 | 326 if err != nil { |
| paddy@23 | 327 if ctx.Log != nil { |
| paddy@23 | 328 ctx.Log.Printf("Error writing access token: %s\n", err) |
| paddy@23 | 329 } |
| paddy@23 | 330 return InternalServerError |
| paddy@23 | 331 } |
| paddy@23 | 332 |
| paddy@23 | 333 // remove authorization token |
| paddy@23 | 334 if data.PreviousAuthorizeData != nil { |
| paddy@23 | 335 err = ctx.Tokens.RemoveAuthorization(data.PreviousAuthorizeData.Code) |
| paddy@23 | 336 if err != nil && ctx.Log != nil { |
| paddy@23 | 337 ctx.Log.Printf("Error removing previous auth data (%s): %s\n", data.PreviousAuthorizeData.Code, err) |
| paddy@23 | 338 } |
| paddy@23 | 339 } |
| paddy@23 | 340 |
| paddy@23 | 341 // remove previous access token |
| paddy@23 | 342 if data.PreviousAccessData != nil { |
| paddy@23 | 343 if data.PreviousAccessData.RefreshToken != "" { |
| paddy@23 | 344 err = ctx.Tokens.RemoveRefresh(data.PreviousAccessData.RefreshToken) |
| paddy@23 | 345 if err != nil && ctx.Log != nil { |
| paddy@23 | 346 ctx.Log.Printf("Error removing previous refresh token (%s): %s\n", data.PreviousAccessData.RefreshToken, err) |
| paddy@23 | 347 } |
| paddy@23 | 348 } |
| paddy@23 | 349 err = ctx.Tokens.RemoveAccess(data.PreviousAccessData.AccessToken) |
| paddy@23 | 350 if err != nil && ctx.Log != nil { |
| paddy@23 | 351 ctx.Log.Printf("Error removing previous access token (%s): %s\n", data.PreviousAccessData.AccessToken, err) |
| paddy@23 | 352 } |
| paddy@23 | 353 } |
| paddy@23 | 354 |
| paddy@23 | 355 data.TokenType = ctx.Config.TokenType |
| paddy@23 | 356 data.ExpiresIn = ctx.Config.AccessExpiration |
| paddy@23 | 357 data.CreatedAt = time.Now() |
| paddy@23 | 358 return nil |
| paddy@23 | 359 } |
| paddy@23 | 360 |
| paddy@23 | 361 func (data AccessData) GetRedirect(fragment bool) (string, error) { |
| paddy@23 | 362 u, err := url.Parse(data.RedirectURI) |
| paddy@23 | 363 if err != nil { |
| paddy@23 | 364 return "", err |
| paddy@23 | 365 } |
| paddy@23 | 366 |
| paddy@23 | 367 // add parameters |
| paddy@23 | 368 q := u.Query() |
| paddy@23 | 369 q.Set("access_token", data.AccessToken) |
| paddy@23 | 370 q.Set("token_type", data.TokenType) |
| paddy@23 | 371 q.Set("expires_in", strconv.FormatInt(int64(data.ExpiresIn), 10)) |
| paddy@23 | 372 if data.RefreshToken != "" { |
| paddy@23 | 373 q.Set("refresh_token", data.RefreshToken) |
| paddy@23 | 374 } |
| paddy@23 | 375 if data.Scope != "" { |
| paddy@23 | 376 q.Set("scope", data.Scope) |
| paddy@23 | 377 } |
| paddy@23 | 378 if len(data.ProfileID) > 0 { |
| paddy@23 | 379 q.Set("profile", data.ProfileID.String()) |
| paddy@23 | 380 } |
| paddy@23 | 381 if fragment { |
| paddy@23 | 382 u.RawQuery = "" |
| paddy@23 | 383 u.Fragment = q.Encode() |
| paddy@23 | 384 } else { |
| paddy@23 | 385 u.RawQuery = q.Encode() |
| paddy@23 | 386 } |
| paddy@23 | 387 |
| paddy@23 | 388 return u.String(), nil |
| paddy@23 | 389 } |
| paddy@23 | 390 |
| paddy@23 | 391 // getClient looks up and authenticates the basic auth using the given |
| paddy@23 | 392 // storage. Sets an error on the response if auth fails or a server error occurs. |
| paddy@23 | 393 func getClient(auth BasicAuth, ctx Context) (Client, error) { |
| paddy@23 | 394 id, err := uuid.Parse(auth.Username) |
| paddy@23 | 395 if err != nil { |
| paddy@23 | 396 return Client{}, err |
| paddy@23 | 397 } |
| paddy@23 | 398 client, err := ctx.Clients.GetClient(id) |
| paddy@23 | 399 if err != nil { |
| paddy@23 | 400 if err == ClientNotFoundError { |
| paddy@23 | 401 return Client{}, err |
| paddy@23 | 402 } |
| paddy@23 | 403 if ctx.Log != nil { |
| paddy@23 | 404 ctx.Log.Printf("Error retrieving client %s: %s", id, err) |
| paddy@23 | 405 } |
| paddy@23 | 406 return Client{}, InternalServerError |
| paddy@23 | 407 } |
| paddy@23 | 408 if client.Secret != auth.Password { |
| paddy@23 | 409 return Client{}, InvalidClientError |
| paddy@23 | 410 } |
| paddy@23 | 411 if client.RedirectURI == "" { |
| paddy@23 | 412 return Client{}, InvalidClientError |
| paddy@23 | 413 } |
| paddy@23 | 414 return client, nil |
| paddy@23 | 415 } |