auth

Paddy 2015-05-17 Parent:cf1aef6eb81f Child:b7e685839a1b

169:37a42585660e Go to Latest

auth/authcode.go

Create interfaces for login verification flow. We needed an interface that we could use to say "send the email to verify the user's login" so that we could verify the emails we have are actually valid. This implements an NSQ version that sends an email_verification event. We'll get listener implementations that pull these messages off NSQ and actually send the emails. This also implements, for testing purposes, a version that just echoes the Login Value and the Verification code to stdout.

History
paddy@26 1 package auth
paddy@26 2
paddy@26 3 import (
paddy@84 4 "encoding/json"
paddy@29 5 "errors"
paddy@84 6 "net/http"
paddy@26 7 "time"
paddy@26 8
paddy@107 9 "code.secondbit.org/uuid.hg"
paddy@26 10 )
paddy@26 11
paddy@84 12 func init() {
paddy@84 13 RegisterGrantType("authorization_code", GrantType{
paddy@84 14 Validate: authCodeGrantValidate,
paddy@94 15 Invalidate: authCodeGrantInvalidate,
paddy@84 16 IssuesRefresh: true,
paddy@85 17 ReturnToken: RenderJSONToken,
paddy@123 18 AllowsPublic: true,
paddy@124 19 AuditString: authCodeGrantAuditString,
paddy@84 20 })
paddy@84 21 }
paddy@84 22
paddy@29 23 var (
paddy@87 24 // ErrNoAuthorizationCodeStore is returned when a Context tries to act on a authorizationCodeStore without setting one first.
paddy@87 25 ErrNoAuthorizationCodeStore = errors.New("no authorizationCodeStore was specified for the Context")
paddy@87 26 // ErrAuthorizationCodeNotFound is returned when an AuthorizationCode is requested but not found in the authorizationCodeStore.
paddy@87 27 ErrAuthorizationCodeNotFound = errors.New("authorization code not found in authorizationCodeStore")
paddy@87 28 // ErrAuthorizationCodeAlreadyExists is returned when an AuthorizationCode is added to a authorizationCodeStore, but another AuthorizationCode with the
paddy@87 29 // same Code already exists in the authorizationCodeStore.
paddy@87 30 ErrAuthorizationCodeAlreadyExists = errors.New("authorization code already exists in authorizationCodeStore")
paddy@29 31 )
paddy@29 32
paddy@87 33 // AuthorizationCode represents an authorization grant made by a user to a Client, to
paddy@57 34 // access user data within a defined Scope for a limited amount of time.
paddy@87 35 type AuthorizationCode struct {
paddy@26 36 Code string
paddy@26 37 Created time.Time
paddy@26 38 ExpiresIn int32
paddy@26 39 ClientID uuid.ID
paddy@163 40 Scopes Scopes
paddy@26 41 RedirectURI string
paddy@26 42 State string
paddy@69 43 ProfileID uuid.ID
paddy@94 44 Used bool
paddy@26 45 }
paddy@26 46
paddy@87 47 type authorizationCodeStore interface {
paddy@87 48 getAuthorizationCode(code string) (AuthorizationCode, error)
paddy@87 49 saveAuthorizationCode(authCode AuthorizationCode) error
paddy@87 50 deleteAuthorizationCode(code string) error
paddy@163 51 deleteAuthorizationCodesByProfileID(profileID uuid.ID) error
paddy@164 52 deleteAuthorizationCodesByClientID(clientID uuid.ID) error
paddy@94 53 useAuthorizationCode(code string) error
paddy@26 54 }
paddy@29 55
paddy@87 56 func (m *memstore) getAuthorizationCode(code string) (AuthorizationCode, error) {
paddy@87 57 m.authCodeLock.RLock()
paddy@87 58 defer m.authCodeLock.RUnlock()
paddy@87 59 authCode, ok := m.authCodes[code]
paddy@29 60 if !ok {
paddy@87 61 return AuthorizationCode{}, ErrAuthorizationCodeNotFound
paddy@29 62 }
paddy@87 63 return authCode, nil
paddy@29 64 }
paddy@29 65
paddy@87 66 func (m *memstore) saveAuthorizationCode(authCode AuthorizationCode) error {
paddy@87 67 m.authCodeLock.Lock()
paddy@87 68 defer m.authCodeLock.Unlock()
paddy@87 69 _, ok := m.authCodes[authCode.Code]
paddy@29 70 if ok {
paddy@87 71 return ErrAuthorizationCodeAlreadyExists
paddy@29 72 }
paddy@87 73 m.authCodes[authCode.Code] = authCode
paddy@29 74 return nil
paddy@29 75 }
paddy@29 76
paddy@87 77 func (m *memstore) deleteAuthorizationCode(code string) error {
paddy@87 78 m.authCodeLock.Lock()
paddy@87 79 defer m.authCodeLock.Unlock()
paddy@87 80 _, ok := m.authCodes[code]
paddy@29 81 if !ok {
paddy@87 82 return ErrAuthorizationCodeNotFound
paddy@29 83 }
paddy@87 84 delete(m.authCodes, code)
paddy@29 85 return nil
paddy@29 86 }
paddy@84 87
paddy@163 88 func (m *memstore) deleteAuthorizationCodesByProfileID(profileID uuid.ID) error {
paddy@163 89 m.authCodeLock.Lock()
paddy@163 90 defer m.authCodeLock.Unlock()
paddy@163 91 var codes []string
paddy@163 92 for _, code := range m.authCodes {
paddy@163 93 if code.ProfileID.Equal(profileID) {
paddy@163 94 codes = append(codes, code.Code)
paddy@163 95 }
paddy@163 96 }
paddy@163 97 if len(codes) < 1 {
paddy@163 98 return ErrProfileNotFound
paddy@163 99 }
paddy@163 100 for _, code := range codes {
paddy@163 101 delete(m.authCodes, code)
paddy@163 102 }
paddy@163 103 return nil
paddy@163 104 }
paddy@163 105
paddy@164 106 func (m *memstore) deleteAuthorizationCodesByClientID(clientID uuid.ID) error {
paddy@164 107 m.authCodeLock.Lock()
paddy@164 108 defer m.authCodeLock.Unlock()
paddy@164 109 var codes []string
paddy@164 110 for _, code := range m.authCodes {
paddy@164 111 if code.ClientID.Equal(clientID) {
paddy@164 112 codes = append(codes, code.Code)
paddy@164 113 }
paddy@164 114 }
paddy@164 115 if len(codes) < 1 {
paddy@164 116 return ErrClientNotFound
paddy@164 117 }
paddy@164 118 for _, code := range codes {
paddy@164 119 delete(m.authCodes, code)
paddy@164 120 }
paddy@164 121 return nil
paddy@164 122 }
paddy@164 123
paddy@94 124 func (m *memstore) useAuthorizationCode(code string) error {
paddy@94 125 m.authCodeLock.Lock()
paddy@94 126 defer m.authCodeLock.Unlock()
paddy@94 127 a, ok := m.authCodes[code]
paddy@94 128 if !ok {
paddy@94 129 return ErrAuthorizationCodeNotFound
paddy@94 130 }
paddy@94 131 a.Used = true
paddy@94 132 m.authCodes[code] = a
paddy@94 133 return nil
paddy@94 134 }
paddy@94 135
paddy@163 136 func authCodeGrantValidate(w http.ResponseWriter, r *http.Request, context Context) (scopes Scopes, profileID uuid.ID, valid bool) {
paddy@84 137 enc := json.NewEncoder(w)
paddy@84 138 code := r.PostFormValue("code")
paddy@84 139 if code == "" {
paddy@84 140 w.WriteHeader(http.StatusBadRequest)
paddy@84 141 renderJSONError(enc, "invalid_request")
paddy@84 142 return
paddy@84 143 }
paddy@123 144 clientID, _, ok := getClientAuth(w, r, true)
paddy@123 145 if !ok {
paddy@84 146 return
paddy@84 147 }
paddy@87 148 authCode, err := context.GetAuthorizationCode(code)
paddy@84 149 if err != nil {
paddy@87 150 if err == ErrAuthorizationCodeNotFound {
paddy@84 151 w.WriteHeader(http.StatusBadRequest)
paddy@84 152 renderJSONError(enc, "invalid_grant")
paddy@84 153 return
paddy@84 154 }
paddy@84 155 w.WriteHeader(http.StatusInternalServerError)
paddy@84 156 renderJSONError(enc, "server_error")
paddy@84 157 return
paddy@84 158 }
paddy@85 159 redirectURI := r.PostFormValue("redirect_uri")
paddy@87 160 if authCode.RedirectURI != redirectURI {
paddy@84 161 w.WriteHeader(http.StatusBadRequest)
paddy@84 162 renderJSONError(enc, "invalid_grant")
paddy@84 163 return
paddy@84 164 }
paddy@87 165 if !authCode.ClientID.Equal(clientID) {
paddy@84 166 w.WriteHeader(http.StatusBadRequest)
paddy@84 167 renderJSONError(enc, "invalid_grant")
paddy@84 168 return
paddy@84 169 }
paddy@135 170 return authCode.Scopes, authCode.ProfileID, true
paddy@84 171 }
paddy@90 172
paddy@90 173 func authCodeGrantInvalidate(r *http.Request, context Context) error {
paddy@94 174 code := r.PostFormValue("code")
paddy@94 175 if code == "" {
paddy@94 176 return ErrAuthorizationCodeNotFound
paddy@94 177 }
paddy@94 178 return context.UseAuthorizationCode(code)
paddy@90 179 }
paddy@124 180
paddy@124 181 func authCodeGrantAuditString(r *http.Request) string {
paddy@124 182 return "authcode:" + r.PostFormValue("code")
paddy@124 183 }