auth
2015-12-14
Parent:b7e685839a1b
auth/session.go
Update nsq import path. go-nsq has moved to nsqio/go-nsq, so we need to update the import path appropriately.
| paddy@70 | 1 package auth |
| paddy@70 | 2 |
| paddy@70 | 3 import ( |
| paddy@132 | 4 "crypto/rand" |
| paddy@98 | 5 "crypto/sha256" |
| paddy@132 | 6 "encoding/base64" |
| paddy@98 | 7 "encoding/hex" |
| paddy@119 | 8 "encoding/json" |
| paddy@70 | 9 "errors" |
| paddy@98 | 10 "log" |
| paddy@98 | 11 "net/http" |
| paddy@89 | 12 "sort" |
| paddy@135 | 13 "strings" |
| paddy@70 | 14 "time" |
| paddy@70 | 15 |
| paddy@107 | 16 "code.secondbit.org/pass.hg" |
| paddy@181 | 17 "code.secondbit.org/scopes.hg/types" |
| paddy@107 | 18 "code.secondbit.org/uuid.hg" |
| paddy@98 | 19 "github.com/gorilla/mux" |
| paddy@98 | 20 ) |
| paddy@98 | 21 |
| paddy@98 | 22 const ( |
| paddy@132 | 23 authCookieName = "auth" |
| paddy@98 | 24 loginTemplateName = "login" |
| paddy@70 | 25 ) |
| paddy@70 | 26 |
| paddy@119 | 27 func init() { |
| paddy@119 | 28 RegisterGrantType("password", GrantType{ |
| paddy@119 | 29 Validate: credentialsValidate, |
| paddy@119 | 30 Invalidate: nil, |
| paddy@119 | 31 IssuesRefresh: true, |
| paddy@119 | 32 ReturnToken: RenderJSONToken, |
| paddy@124 | 33 AuditString: credentialsAuditString, |
| paddy@119 | 34 }) |
| paddy@119 | 35 } |
| paddy@119 | 36 |
| paddy@70 | 37 var ( |
| paddy@70 | 38 // ErrNoSessionStore is returned when a Context tries to act on a sessionStore without setting on first. |
| paddy@70 | 39 ErrNoSessionStore = errors.New("no sessionStore was specified for the Context") |
| paddy@70 | 40 // ErrSessionNotFound is returned when a Session is requested but not found in the sessionStore. |
| paddy@70 | 41 ErrSessionNotFound = errors.New("session not found in sessionStore") |
| paddy@70 | 42 // ErrInvalidSession is returned when a Session is specified but is not valid. |
| paddy@70 | 43 ErrInvalidSession = errors.New("session is not valid") |
| paddy@77 | 44 // ErrSessionAlreadyExists is returned when a sessionStore tries to store a Session with an ID that already exists in the sessionStore. |
| paddy@77 | 45 ErrSessionAlreadyExists = errors.New("session already exists") |
| paddy@132 | 46 // ErrCSRFAttempt is returned when a CSRF attempt is detected. |
| paddy@132 | 47 ErrCSRFAttempt = errors.New("CSRF attempt") |
| paddy@98 | 48 |
| paddy@98 | 49 passphraseSchemes = map[int]passphraseScheme{ |
| paddy@98 | 50 1: { |
| paddy@98 | 51 check: pbkdf2sha256check, |
| paddy@98 | 52 create: pbkdf2sha256create, |
| paddy@98 | 53 calculateIterations: pbkdf2sha256calc, |
| paddy@98 | 54 }, |
| paddy@98 | 55 } |
| paddy@70 | 56 ) |
| paddy@70 | 57 |
| paddy@98 | 58 type passphraseScheme struct { |
| paddy@98 | 59 check func(profile Profile, passphrase string) (bool, error) |
| paddy@103 | 60 create func(passphrase string, iterations int) (result, salt string, err error) |
| paddy@98 | 61 calculateIterations func() (int, error) |
| paddy@98 | 62 } |
| paddy@98 | 63 |
| paddy@70 | 64 // Session represents a user's authenticated session, associating it with a profile |
| paddy@70 | 65 // and some audit data. |
| paddy@70 | 66 type Session struct { |
| paddy@70 | 67 ID string |
| paddy@70 | 68 IP string |
| paddy@70 | 69 UserAgent string |
| paddy@70 | 70 ProfileID uuid.ID |
| paddy@98 | 71 Login string |
| paddy@70 | 72 Created time.Time |
| paddy@132 | 73 Expires time.Time |
| paddy@70 | 74 Active bool |
| paddy@132 | 75 CSRFToken string |
| paddy@70 | 76 } |
| paddy@70 | 77 |
| paddy@89 | 78 type sortedSessions []Session |
| paddy@89 | 79 |
| paddy@89 | 80 func (s sortedSessions) Len() int { |
| paddy@89 | 81 return len(s) |
| paddy@89 | 82 } |
| paddy@89 | 83 |
| paddy@89 | 84 func (s sortedSessions) Less(i, j int) bool { |
| paddy@89 | 85 return s[i].Created.After(s[j].Created) |
| paddy@89 | 86 } |
| paddy@89 | 87 |
| paddy@89 | 88 func (s sortedSessions) Swap(i, j int) { |
| paddy@89 | 89 s[i], s[j] = s[j], s[i] |
| paddy@89 | 90 } |
| paddy@89 | 91 |
| paddy@70 | 92 type sessionStore interface { |
| paddy@70 | 93 createSession(session Session) error |
| paddy@70 | 94 getSession(id string) (Session, error) |
| paddy@159 | 95 terminateSession(id string) error |
| paddy@70 | 96 removeSession(id string) error |
| paddy@70 | 97 listSessions(profile uuid.ID, before time.Time, num int64) ([]Session, error) |
| paddy@162 | 98 terminateSessionsByProfile(profile uuid.ID) error |
| paddy@70 | 99 } |
| paddy@77 | 100 |
| paddy@77 | 101 func (m *memstore) createSession(session Session) error { |
| paddy@77 | 102 m.sessionLock.Lock() |
| paddy@77 | 103 defer m.sessionLock.Unlock() |
| paddy@77 | 104 if _, ok := m.sessions[session.ID]; ok { |
| paddy@77 | 105 return ErrSessionAlreadyExists |
| paddy@77 | 106 } |
| paddy@77 | 107 m.sessions[session.ID] = session |
| paddy@77 | 108 return nil |
| paddy@77 | 109 } |
| paddy@77 | 110 |
| paddy@77 | 111 func (m *memstore) getSession(id string) (Session, error) { |
| paddy@77 | 112 m.sessionLock.RLock() |
| paddy@77 | 113 defer m.sessionLock.RUnlock() |
| paddy@77 | 114 if _, ok := m.sessions[id]; !ok { |
| paddy@77 | 115 return Session{}, ErrSessionNotFound |
| paddy@77 | 116 } |
| paddy@77 | 117 return m.sessions[id], nil |
| paddy@77 | 118 } |
| paddy@77 | 119 |
| paddy@159 | 120 func (m *memstore) terminateSession(id string) error { |
| paddy@159 | 121 m.sessionLock.RLock() |
| paddy@159 | 122 defer m.sessionLock.RUnlock() |
| paddy@159 | 123 sess, ok := m.sessions[id] |
| paddy@159 | 124 if !ok { |
| paddy@159 | 125 return ErrSessionNotFound |
| paddy@159 | 126 } |
| paddy@159 | 127 sess.Active = false |
| paddy@159 | 128 m.sessions[id] = sess |
| paddy@159 | 129 return nil |
| paddy@159 | 130 } |
| paddy@159 | 131 |
| paddy@162 | 132 func (m *memstore) terminateSessionsByProfile(profile uuid.ID) error { |
| paddy@162 | 133 m.sessionLock.RLock() |
| paddy@162 | 134 defer m.sessionLock.RUnlock() |
| paddy@162 | 135 var found bool |
| paddy@162 | 136 for _, session := range m.sessions { |
| paddy@162 | 137 if profile.Equal(session.ProfileID) { |
| paddy@162 | 138 session.Active = false |
| paddy@162 | 139 m.sessions[session.ID] = session |
| paddy@162 | 140 found = true |
| paddy@162 | 141 } |
| paddy@162 | 142 } |
| paddy@162 | 143 if !found { |
| paddy@162 | 144 return ErrProfileNotFound |
| paddy@162 | 145 } |
| paddy@162 | 146 return nil |
| paddy@162 | 147 } |
| paddy@162 | 148 |
| paddy@77 | 149 func (m *memstore) removeSession(id string) error { |
| paddy@77 | 150 m.sessionLock.Lock() |
| paddy@77 | 151 defer m.sessionLock.Unlock() |
| paddy@77 | 152 if _, ok := m.sessions[id]; !ok { |
| paddy@77 | 153 return ErrSessionNotFound |
| paddy@77 | 154 } |
| paddy@77 | 155 delete(m.sessions, id) |
| paddy@77 | 156 return nil |
| paddy@77 | 157 } |
| paddy@77 | 158 |
| paddy@77 | 159 func (m *memstore) listSessions(profile uuid.ID, before time.Time, num int64) ([]Session, error) { |
| paddy@77 | 160 m.sessionLock.RLock() |
| paddy@77 | 161 defer m.sessionLock.RUnlock() |
| paddy@77 | 162 res := []Session{} |
| paddy@77 | 163 for _, session := range m.sessions { |
| paddy@77 | 164 if int64(len(res)) >= num { |
| paddy@77 | 165 break |
| paddy@77 | 166 } |
| paddy@77 | 167 if profile != nil && !profile.Equal(session.ProfileID) { |
| paddy@77 | 168 continue |
| paddy@77 | 169 } |
| paddy@77 | 170 if !before.IsZero() && session.Created.After(before) { |
| paddy@77 | 171 continue |
| paddy@77 | 172 } |
| paddy@77 | 173 res = append(res, session) |
| paddy@77 | 174 } |
| paddy@89 | 175 sorted := sortedSessions(res) |
| paddy@89 | 176 sort.Sort(sorted) |
| paddy@89 | 177 res = []Session(sorted) |
| paddy@77 | 178 return res, nil |
| paddy@77 | 179 } |
| paddy@98 | 180 |
| paddy@98 | 181 // RegisterSessionHandlers adds handlers to the passed router to handle the session endpoints, like login and logout. |
| paddy@98 | 182 func RegisterSessionHandlers(r *mux.Router, context Context) { |
| paddy@98 | 183 r.Handle("/login", wrap(context, CreateSessionHandler)) |
| paddy@128 | 184 // BUG(paddy): We need to implement a handler for listing sessions active on a profile. |
| paddy@159 | 185 r.Handle("/sessions/{id}", wrap(context, TerminateSessionHandler)).Methods("OPTIONS", "DELETE") |
| paddy@98 | 186 } |
| paddy@98 | 187 |
| paddy@132 | 188 func checkCSRF(r *http.Request, s Session) error { |
| paddy@132 | 189 if r.PostFormValue("csrftoken") != s.CSRFToken { |
| paddy@132 | 190 return ErrCSRFAttempt |
| paddy@132 | 191 } |
| paddy@132 | 192 return nil |
| paddy@132 | 193 } |
| paddy@132 | 194 |
| paddy@98 | 195 func checkCookie(r *http.Request, context Context) (Session, error) { |
| paddy@98 | 196 cookie, err := r.Cookie(authCookieName) |
| paddy@98 | 197 if err == http.ErrNoCookie { |
| paddy@98 | 198 return Session{}, ErrNoSession |
| paddy@98 | 199 } else if err != nil { |
| paddy@98 | 200 log.Println(err) |
| paddy@98 | 201 return Session{}, err |
| paddy@98 | 202 } |
| paddy@98 | 203 sess, err := context.GetSession(cookie.Value) |
| paddy@98 | 204 if err == ErrSessionNotFound { |
| paddy@98 | 205 return Session{}, ErrInvalidSession |
| paddy@98 | 206 } else if err != nil { |
| paddy@98 | 207 return Session{}, err |
| paddy@98 | 208 } |
| paddy@98 | 209 if !sess.Active { |
| paddy@98 | 210 return Session{}, ErrInvalidSession |
| paddy@98 | 211 } |
| paddy@132 | 212 if time.Now().After(sess.Expires) { |
| paddy@132 | 213 return Session{}, ErrInvalidSession |
| paddy@132 | 214 } |
| paddy@98 | 215 return sess, nil |
| paddy@98 | 216 } |
| paddy@98 | 217 |
| paddy@98 | 218 func buildLoginRedirect(r *http.Request, context Context) string { |
| paddy@98 | 219 if context.loginURI == nil { |
| paddy@98 | 220 return "" |
| paddy@98 | 221 } |
| paddy@98 | 222 uri := *context.loginURI |
| paddy@98 | 223 q := uri.Query() |
| paddy@98 | 224 q.Set("from", r.URL.String()) |
| paddy@98 | 225 uri.RawQuery = q.Encode() |
| paddy@98 | 226 return uri.String() |
| paddy@98 | 227 } |
| paddy@98 | 228 |
| paddy@98 | 229 func pbkdf2sha256check(profile Profile, passphrase string) (bool, error) { |
| paddy@98 | 230 realPass, err := hex.DecodeString(profile.Passphrase) |
| paddy@98 | 231 if err != nil { |
| paddy@98 | 232 return false, err |
| paddy@98 | 233 } |
| paddy@103 | 234 realSalt, err := hex.DecodeString(profile.Salt) |
| paddy@103 | 235 if err != nil { |
| paddy@103 | 236 return false, err |
| paddy@103 | 237 } |
| paddy@103 | 238 candidate := pass.Check(sha256.New, profile.Iterations, []byte(passphrase), []byte(realSalt)) |
| paddy@98 | 239 if !pass.Compare(candidate, realPass) { |
| paddy@98 | 240 return false, ErrIncorrectAuth |
| paddy@98 | 241 } |
| paddy@98 | 242 return true, nil |
| paddy@98 | 243 } |
| paddy@98 | 244 |
| paddy@103 | 245 func pbkdf2sha256create(passphrase string, iters int) (result, salt string, err error) { |
| paddy@103 | 246 passBytes, saltBytes, err := pass.Create(sha256.New, iters, []byte(passphrase)) |
| paddy@103 | 247 if err != nil { |
| paddy@103 | 248 return "", "", err |
| paddy@103 | 249 } |
| paddy@103 | 250 result = hex.EncodeToString(passBytes) |
| paddy@103 | 251 salt = hex.EncodeToString(saltBytes) |
| paddy@103 | 252 return result, salt, err |
| paddy@98 | 253 } |
| paddy@98 | 254 |
| paddy@98 | 255 func pbkdf2sha256calc() (int, error) { |
| paddy@98 | 256 return pass.CalculateIterations(sha256.New) |
| paddy@98 | 257 } |
| paddy@98 | 258 |
| paddy@139 | 259 func isAuthError(err error) bool { |
| paddy@139 | 260 return err == ErrIncorrectAuth || err == ErrProfileCompromised || err == ErrProfileLocked || err == ErrInvalidPassphraseScheme |
| paddy@139 | 261 } |
| paddy@139 | 262 |
| paddy@98 | 263 func authenticate(user, passphrase string, context Context) (Profile, error) { |
| paddy@98 | 264 profile, err := context.GetProfileByLogin(user) |
| paddy@98 | 265 if err != nil { |
| paddy@98 | 266 if err == ErrProfileNotFound || err == ErrLoginNotFound { |
| paddy@98 | 267 return Profile{}, ErrIncorrectAuth |
| paddy@98 | 268 } |
| paddy@98 | 269 return Profile{}, err |
| paddy@98 | 270 } |
| paddy@98 | 271 if profile.Compromised { |
| paddy@98 | 272 return Profile{}, ErrProfileCompromised |
| paddy@98 | 273 } |
| paddy@98 | 274 if !profile.LockedUntil.IsZero() && profile.LockedUntil.After(time.Now()) { |
| paddy@98 | 275 return profile, ErrProfileLocked |
| paddy@98 | 276 } |
| paddy@98 | 277 scheme, ok := passphraseSchemes[profile.PassphraseScheme] |
| paddy@98 | 278 if !ok { |
| paddy@98 | 279 return Profile{}, ErrInvalidPassphraseScheme |
| paddy@98 | 280 } |
| paddy@98 | 281 result, err := scheme.check(profile, passphrase) |
| paddy@98 | 282 if !result { |
| paddy@98 | 283 return Profile{}, err |
| paddy@98 | 284 } |
| paddy@98 | 285 return profile, nil |
| paddy@98 | 286 } |
| paddy@98 | 287 |
| paddy@98 | 288 // CreateSessionHandler allows the user to log into their account and create their session. |
| paddy@98 | 289 func CreateSessionHandler(w http.ResponseWriter, r *http.Request, context Context) { |
| paddy@98 | 290 errors := []error{} |
| paddy@98 | 291 if r.Method == "POST" { |
| paddy@98 | 292 profile, err := authenticate(r.PostFormValue("login"), r.PostFormValue("passphrase"), context) |
| paddy@98 | 293 if err == nil { |
| paddy@98 | 294 ip := r.Header.Get("X-Forwarded-For") |
| paddy@98 | 295 if ip == "" { |
| paddy@98 | 296 ip = r.RemoteAddr |
| paddy@98 | 297 } |
| paddy@132 | 298 sessionID := make([]byte, 32) |
| paddy@132 | 299 csrfToken := make([]byte, 32) |
| paddy@132 | 300 _, err = rand.Read(sessionID) |
| paddy@132 | 301 if err != nil { |
| paddy@132 | 302 log.Println("Error reading CSPRNG for session ID:", err) |
| paddy@132 | 303 w.WriteHeader(http.StatusInternalServerError) |
| paddy@132 | 304 w.Write([]byte("Internal error")) |
| paddy@132 | 305 return |
| paddy@132 | 306 } |
| paddy@132 | 307 _, err = rand.Read(csrfToken) |
| paddy@132 | 308 if err != nil { |
| paddy@132 | 309 log.Println("Error reading CSPRNG for CSRF token:", err) |
| paddy@132 | 310 w.WriteHeader(http.StatusInternalServerError) |
| paddy@132 | 311 w.Write([]byte("internal error")) |
| paddy@132 | 312 return |
| paddy@132 | 313 } |
| paddy@98 | 314 session := Session{ |
| paddy@159 | 315 ID: base64.URLEncoding.EncodeToString(sessionID), |
| paddy@98 | 316 IP: ip, |
| paddy@98 | 317 UserAgent: r.UserAgent(), |
| paddy@98 | 318 ProfileID: profile.ID, |
| paddy@98 | 319 Login: r.PostFormValue("login"), |
| paddy@98 | 320 Created: time.Now(), |
| paddy@132 | 321 Expires: time.Now().Add(time.Hour), |
| paddy@98 | 322 Active: true, |
| paddy@159 | 323 CSRFToken: base64.URLEncoding.EncodeToString(csrfToken), |
| paddy@98 | 324 } |
| paddy@98 | 325 err = context.CreateSession(session) |
| paddy@98 | 326 if err != nil { |
| paddy@98 | 327 w.WriteHeader(http.StatusInternalServerError) |
| paddy@98 | 328 w.Write([]byte(err.Error())) |
| paddy@98 | 329 return |
| paddy@98 | 330 } |
| paddy@132 | 331 // BUG(paddy): We really need to do a security audit on our cookies. |
| paddy@98 | 332 cookie := http.Cookie{ |
| paddy@98 | 333 Name: authCookieName, |
| paddy@98 | 334 Value: session.ID, |
| paddy@132 | 335 Expires: session.Expires, |
| paddy@98 | 336 HttpOnly: true, |
| paddy@132 | 337 Secure: context.config.secureCookie, |
| paddy@98 | 338 } |
| paddy@98 | 339 http.SetCookie(w, &cookie) |
| paddy@98 | 340 redirectTo := r.URL.Query().Get("from") |
| paddy@98 | 341 if redirectTo == "" { |
| paddy@98 | 342 redirectTo = "/" |
| paddy@98 | 343 } |
| paddy@98 | 344 http.Redirect(w, r, redirectTo, http.StatusFound) |
| paddy@98 | 345 return |
| paddy@139 | 346 } else if !isAuthError(err) { |
| paddy@98 | 347 w.WriteHeader(http.StatusInternalServerError) |
| paddy@98 | 348 w.Write([]byte(err.Error())) |
| paddy@98 | 349 return |
| paddy@98 | 350 } else { |
| paddy@98 | 351 errors = append(errors, err) |
| paddy@98 | 352 } |
| paddy@98 | 353 } |
| paddy@98 | 354 context.Render(w, loginTemplateName, map[string]interface{}{ |
| paddy@98 | 355 "errors": errors, |
| paddy@98 | 356 }) |
| paddy@98 | 357 } |
| paddy@119 | 358 |
| paddy@159 | 359 // TerminateSessionHandler allows the user to end their session before it expires. |
| paddy@159 | 360 func TerminateSessionHandler(w http.ResponseWriter, r *http.Request, context Context) { |
| paddy@172 | 361 var errors []RequestError |
| paddy@159 | 362 vars := mux.Vars(r) |
| paddy@159 | 363 if vars["id"] == "" { |
| paddy@172 | 364 errors = append(errors, RequestError{Slug: RequestErrMissing, Param: "id"}) |
| paddy@172 | 365 encode(w, r, http.StatusBadRequest, Response{Errors: errors}) |
| paddy@159 | 366 return |
| paddy@159 | 367 } |
| paddy@159 | 368 id := vars["id"] |
| paddy@159 | 369 un, pw, ok := r.BasicAuth() |
| paddy@159 | 370 if !ok { |
| paddy@172 | 371 errors = append(errors, RequestError{Slug: RequestErrAccessDenied}) |
| paddy@172 | 372 encode(w, r, http.StatusUnauthorized, Response{Errors: errors}) |
| paddy@159 | 373 return |
| paddy@159 | 374 } |
| paddy@159 | 375 profile, err := authenticate(un, pw, context) |
| paddy@159 | 376 if err != nil { |
| paddy@159 | 377 if isAuthError(err) { |
| paddy@172 | 378 errors = append(errors, RequestError{Slug: RequestErrAccessDenied}) |
| paddy@172 | 379 encode(w, r, http.StatusForbidden, Response{Errors: errors}) |
| paddy@159 | 380 return |
| paddy@159 | 381 } |
| paddy@172 | 382 errors = append(errors, RequestError{Slug: RequestErrActOfGod}) |
| paddy@172 | 383 encode(w, r, http.StatusInternalServerError, Response{Errors: errors}) |
| paddy@159 | 384 return |
| paddy@159 | 385 } |
| paddy@159 | 386 session, err := context.GetSession(id) |
| paddy@159 | 387 if err != nil { |
| paddy@159 | 388 if err == ErrSessionNotFound { |
| paddy@172 | 389 errors = append(errors, RequestError{Slug: RequestErrNotFound, Param: "id"}) |
| paddy@172 | 390 encode(w, r, http.StatusNotFound, Response{Errors: errors}) |
| paddy@159 | 391 return |
| paddy@159 | 392 } |
| paddy@172 | 393 errors = append(errors, RequestError{Slug: RequestErrActOfGod}) |
| paddy@172 | 394 encode(w, r, http.StatusInternalServerError, Response{Errors: errors}) |
| paddy@159 | 395 return |
| paddy@159 | 396 } |
| paddy@159 | 397 if !session.ProfileID.Equal(profile.ID) { |
| paddy@172 | 398 errors = append(errors, RequestError{Slug: RequestErrAccessDenied, Param: "id"}) |
| paddy@172 | 399 encode(w, r, http.StatusForbidden, Response{Errors: errors}) |
| paddy@159 | 400 return |
| paddy@159 | 401 } |
| paddy@159 | 402 err = context.TerminateSession(id) |
| paddy@159 | 403 if err != nil { |
| paddy@159 | 404 if err == ErrSessionNotFound { |
| paddy@172 | 405 errors = append(errors, RequestError{Slug: RequestErrNotFound, Param: "id"}) |
| paddy@172 | 406 encode(w, r, http.StatusNotFound, Response{Errors: errors}) |
| paddy@159 | 407 return |
| paddy@159 | 408 } |
| paddy@172 | 409 errors = append(errors, RequestError{Slug: RequestErrActOfGod}) |
| paddy@172 | 410 encode(w, r, http.StatusInternalServerError, Response{Errors: errors}) |
| paddy@159 | 411 return |
| paddy@159 | 412 } |
| paddy@159 | 413 session.Active = false |
| paddy@172 | 414 encode(w, r, http.StatusOK, Response{Sessions: []Session{session}, Errors: errors}) |
| paddy@159 | 415 } |
| paddy@159 | 416 |
| paddy@181 | 417 func credentialsValidate(w http.ResponseWriter, r *http.Request, context Context) (scopes scopeTypes.Scopes, profileID uuid.ID, valid bool) { |
| paddy@119 | 418 enc := json.NewEncoder(w) |
| paddy@119 | 419 username := r.PostFormValue("username") |
| paddy@119 | 420 password := r.PostFormValue("password") |
| paddy@181 | 421 scopes = scopeTypes.StringsToScopes(strings.Split(r.PostFormValue("scope"), " ")) |
| paddy@119 | 422 profile, err := authenticate(username, password, context) |
| paddy@119 | 423 if err != nil { |
| paddy@139 | 424 if isAuthError(err) { |
| paddy@119 | 425 w.WriteHeader(http.StatusBadRequest) |
| paddy@119 | 426 renderJSONError(enc, "invalid_grant") |
| paddy@119 | 427 return |
| paddy@119 | 428 } |
| paddy@119 | 429 w.WriteHeader(http.StatusInternalServerError) |
| paddy@119 | 430 w.Write([]byte(err.Error())) |
| paddy@119 | 431 return |
| paddy@119 | 432 } |
| paddy@119 | 433 profileID = profile.ID |
| paddy@119 | 434 valid = true |
| paddy@119 | 435 return |
| paddy@119 | 436 } |
| paddy@124 | 437 |
| paddy@124 | 438 func credentialsAuditString(r *http.Request) string { |
| paddy@124 | 439 return "credentials" |
| paddy@124 | 440 } |