auth
auth/authcode_test.go
Fix bug when issuing client secret. Client secrets were only issued to public clients, not only to confidential clients. Oops.
| paddy@29 | 1 package auth |
| paddy@29 | 2 |
| paddy@29 | 3 import ( |
| paddy@111 | 4 "bytes" |
| paddy@111 | 5 "io/ioutil" |
| paddy@111 | 6 "net/http" |
| paddy@111 | 7 "net/http/httptest" |
| paddy@111 | 8 "net/url" |
| paddy@111 | 9 "strings" |
| paddy@29 | 10 "testing" |
| paddy@29 | 11 "time" |
| paddy@29 | 12 |
| paddy@107 | 13 "code.secondbit.org/uuid.hg" |
| paddy@29 | 14 ) |
| paddy@29 | 15 |
| paddy@87 | 16 var authCodeStores = []authorizationCodeStore{NewMemstore()} |
| paddy@29 | 17 |
| paddy@87 | 18 func compareAuthorizationCodes(authCode1, authCode2 AuthorizationCode) (success bool, field string, authCode1val, authCode2val interface{}) { |
| paddy@87 | 19 if authCode1.Code != authCode2.Code { |
| paddy@87 | 20 return false, "code", authCode1.Code, authCode2.Code |
| paddy@34 | 21 } |
| paddy@87 | 22 if !authCode1.Created.Equal(authCode2.Created) { |
| paddy@87 | 23 return false, "created", authCode1.Created, authCode2.Created |
| paddy@34 | 24 } |
| paddy@87 | 25 if authCode1.ExpiresIn != authCode2.ExpiresIn { |
| paddy@87 | 26 return false, "expires in", authCode1.ExpiresIn, authCode2.ExpiresIn |
| paddy@34 | 27 } |
| paddy@87 | 28 if !authCode1.ClientID.Equal(authCode2.ClientID) { |
| paddy@87 | 29 return false, "client ID", authCode1.ClientID, authCode2.ClientID |
| paddy@34 | 30 } |
| paddy@87 | 31 if authCode1.Scope != authCode2.Scope { |
| paddy@87 | 32 return false, "scope", authCode1.Scope, authCode2.Scope |
| paddy@34 | 33 } |
| paddy@87 | 34 if authCode1.RedirectURI != authCode2.RedirectURI { |
| paddy@87 | 35 return false, "redirect URI", authCode1.RedirectURI, authCode2.RedirectURI |
| paddy@34 | 36 } |
| paddy@87 | 37 if authCode1.State != authCode2.State { |
| paddy@87 | 38 return false, "state", authCode1.State, authCode2.State |
| paddy@34 | 39 } |
| paddy@111 | 40 if !authCode1.ProfileID.Equal(authCode2.ProfileID) { |
| paddy@111 | 41 return false, "profile ID", authCode1.ProfileID, authCode2.ProfileID |
| paddy@111 | 42 } |
| paddy@111 | 43 if authCode1.Used != authCode2.Used { |
| paddy@111 | 44 return false, "used", authCode1.Used, authCode2.Used |
| paddy@111 | 45 } |
| paddy@34 | 46 return true, "", nil, nil |
| paddy@34 | 47 } |
| paddy@34 | 48 |
| paddy@111 | 49 func TestAuthorizationCodeStore(t *testing.T) { |
| paddy@36 | 50 t.Parallel() |
| paddy@87 | 51 authCode := AuthorizationCode{ |
| paddy@29 | 52 Code: "code", |
| paddy@29 | 53 Created: time.Now(), |
| paddy@29 | 54 ExpiresIn: 180, |
| paddy@29 | 55 ClientID: uuid.NewID(), |
| paddy@29 | 56 Scope: "scope", |
| paddy@29 | 57 RedirectURI: "redirectURI", |
| paddy@29 | 58 State: "state", |
| paddy@29 | 59 } |
| paddy@87 | 60 for _, store := range authCodeStores { |
| paddy@116 | 61 context := Context{authCodes: store} |
| paddy@116 | 62 err := context.SaveAuthorizationCode(authCode) |
| paddy@29 | 63 if err != nil { |
| paddy@87 | 64 t.Errorf("Error saving auth code to %T: %s", store, err) |
| paddy@34 | 65 } |
| paddy@116 | 66 err = context.SaveAuthorizationCode(authCode) |
| paddy@87 | 67 if err != ErrAuthorizationCodeAlreadyExists { |
| paddy@87 | 68 t.Errorf("Expected ErrAuthorizationCodeAlreadyExists from %T, got %+v", store, err) |
| paddy@29 | 69 } |
| paddy@116 | 70 retrieved, err := context.GetAuthorizationCode(authCode.Code) |
| paddy@29 | 71 if err != nil { |
| paddy@87 | 72 t.Errorf("Error retrieving auth code from %T: %s", store, err) |
| paddy@29 | 73 } |
| paddy@87 | 74 match, field, expectation, result := compareAuthorizationCodes(authCode, retrieved) |
| paddy@34 | 75 if !match { |
| paddy@87 | 76 t.Errorf("Expected `%v` in the `%s` field of auth code retrieved from %T, got `%v`", expectation, field, store, result) |
| paddy@34 | 77 } |
| paddy@116 | 78 err = context.UseAuthorizationCode(authCode.Code) |
| paddy@111 | 79 if err != nil { |
| paddy@111 | 80 t.Errorf("Error retrieving auth code from %T: %s", store, err) |
| paddy@111 | 81 } |
| paddy@116 | 82 retrieved, err = context.GetAuthorizationCode(authCode.Code) |
| paddy@111 | 83 if err != nil { |
| paddy@111 | 84 t.Errorf("Error retrieving auth code from %T: %s", store, err) |
| paddy@111 | 85 } |
| paddy@111 | 86 authCode.Used = true |
| paddy@111 | 87 match, field, expectation, result = compareAuthorizationCodes(authCode, retrieved) |
| paddy@111 | 88 if !match { |
| paddy@111 | 89 t.Errorf("Expected `%v` in the `%s` field of auth code retrieved from %T, got `%v`", expectation, field, store, result) |
| paddy@111 | 90 } |
| paddy@116 | 91 err = context.DeleteAuthorizationCode(authCode.Code) |
| paddy@29 | 92 if err != nil { |
| paddy@87 | 93 t.Errorf("Error removing auth code from %T: %s", store, err) |
| paddy@29 | 94 } |
| paddy@116 | 95 retrieved, err = context.GetAuthorizationCode(authCode.Code) |
| paddy@87 | 96 if err != ErrAuthorizationCodeNotFound { |
| paddy@87 | 97 t.Errorf("Expected ErrAuthorizationCodeNotFound from %T, got %+v and %+v", store, retrieved, err) |
| paddy@34 | 98 } |
| paddy@116 | 99 err = context.DeleteAuthorizationCode(authCode.Code) |
| paddy@87 | 100 if err != ErrAuthorizationCodeNotFound { |
| paddy@87 | 101 t.Errorf("Expected ErrAuthorizationCodeNotFound from %T, got %+v", store, err) |
| paddy@29 | 102 } |
| paddy@116 | 103 err = context.UseAuthorizationCode(authCode.Code) |
| paddy@111 | 104 if err != ErrAuthorizationCodeNotFound { |
| paddy@111 | 105 t.Errorf("Expected ErrAuthorizationCodeNotFound from %T, got %+v", store, err) |
| paddy@111 | 106 } |
| paddy@29 | 107 } |
| paddy@29 | 108 } |
| paddy@111 | 109 |
| paddy@111 | 110 func TestAuthCodeGrantValidate(t *testing.T) { |
| paddy@111 | 111 t.Parallel() |
| paddy@111 | 112 store := NewMemstore() |
| paddy@111 | 113 testContext := Context{ |
| paddy@111 | 114 clients: store, |
| paddy@111 | 115 authCodes: store, |
| paddy@111 | 116 profiles: store, |
| paddy@111 | 117 tokens: store, |
| paddy@111 | 118 sessions: store, |
| paddy@111 | 119 } |
| paddy@111 | 120 client := Client{ |
| paddy@111 | 121 ID: uuid.NewID(), |
| paddy@111 | 122 Secret: "super secret!", |
| paddy@111 | 123 OwnerID: uuid.NewID(), |
| paddy@111 | 124 Name: "My test client", |
| paddy@111 | 125 Logo: "https://secondbit.org/logo.png", |
| paddy@111 | 126 Website: "https://secondbit.org/", |
| paddy@111 | 127 Type: "public", |
| paddy@111 | 128 } |
| paddy@111 | 129 endpoint := Endpoint{ |
| paddy@111 | 130 ID: uuid.NewID(), |
| paddy@111 | 131 ClientID: client.ID, |
| paddy@116 | 132 URI: "https://test.secondbit.org/redirect", |
| paddy@111 | 133 Added: time.Now(), |
| paddy@111 | 134 } |
| paddy@116 | 135 err := testContext.SaveClient(client) |
| paddy@111 | 136 if err != nil { |
| paddy@111 | 137 t.Fatal("Can't store client:", err) |
| paddy@111 | 138 } |
| paddy@115 | 139 err = testContext.AddEndpoints(client.ID, []Endpoint{endpoint}) |
| paddy@111 | 140 if err != nil { |
| paddy@111 | 141 t.Fatal("Can't store endpoint:", err) |
| paddy@111 | 142 } |
| paddy@111 | 143 code := AuthorizationCode{ |
| paddy@111 | 144 Code: "myauthcode", |
| paddy@111 | 145 Created: time.Now(), |
| paddy@111 | 146 ExpiresIn: 180, |
| paddy@111 | 147 ClientID: uuid.NewID(), |
| paddy@111 | 148 Scope: "scope", |
| paddy@111 | 149 RedirectURI: "redirectURI", |
| paddy@111 | 150 State: "state", |
| paddy@111 | 151 } |
| paddy@111 | 152 err = testContext.SaveAuthorizationCode(code) |
| paddy@111 | 153 if err != nil { |
| paddy@111 | 154 t.Fatal("Can't add auth code:", err) |
| paddy@111 | 155 } |
| paddy@112 | 156 code2 := code |
| paddy@112 | 157 code2.Code = "otherauthcode" |
| paddy@112 | 158 code2.ClientID = client.ID |
| paddy@112 | 159 err = testContext.SaveAuthorizationCode(code2) |
| paddy@112 | 160 if err != nil { |
| paddy@112 | 161 t.Fatal("Can't add second auth code:", err) |
| paddy@112 | 162 } |
| paddy@111 | 163 req, err := http.NewRequest("POST", "https://test.auth.secondbit.org/oauth2/grant", nil) |
| paddy@111 | 164 if err != nil { |
| paddy@111 | 165 t.Fatal("Can't build request:", err) |
| paddy@111 | 166 } |
| paddy@112 | 167 req.Header.Set("Content-Type", "application/x-www-form-urlencoded") |
| paddy@111 | 168 w := httptest.NewRecorder() |
| paddy@111 | 169 params := url.Values{} |
| paddy@111 | 170 body := bytes.NewBufferString(params.Encode()) |
| paddy@111 | 171 req.Body = ioutil.NopCloser(body) |
| paddy@111 | 172 scope, profileID, valid := authCodeGrantValidate(w, req, testContext) |
| paddy@111 | 173 if valid { |
| paddy@112 | 174 t.Fatalf("Expected invalid auth code, got scope `%s` and profileID `%s`.", scope, profileID) |
| paddy@111 | 175 } |
| paddy@111 | 176 if w.Code != http.StatusBadRequest { |
| paddy@111 | 177 t.Errorf("Expected status %d, got %d", http.StatusBadRequest, w.Code) |
| paddy@111 | 178 } |
| paddy@112 | 179 expectedBody := `{"error":"invalid_request"}` |
| paddy@112 | 180 if strings.TrimSpace(w.Body.String()) != expectedBody { |
| paddy@112 | 181 t.Errorf("Expected body of `%s`, got `%s`", expectedBody, strings.TrimSpace(w.Body.String())) |
| paddy@112 | 182 } |
| paddy@112 | 183 |
| paddy@112 | 184 req, err = http.NewRequest("POST", "https://test.auth.secondbit.org/oauth2/grant", nil) |
| paddy@112 | 185 if err != nil { |
| paddy@112 | 186 t.Fatal("Can't build request:", err) |
| paddy@112 | 187 } |
| paddy@112 | 188 req.Header.Set("Content-Type", "application/x-www-form-urlencoded") |
| paddy@112 | 189 w = httptest.NewRecorder() |
| paddy@112 | 190 params = url.Values{} |
| paddy@112 | 191 params.Set("code", "notmycode") |
| paddy@112 | 192 body = bytes.NewBufferString(params.Encode()) |
| paddy@112 | 193 req.Body = ioutil.NopCloser(body) |
| paddy@112 | 194 err = req.ParseForm() |
| paddy@112 | 195 if err != nil { |
| paddy@112 | 196 t.Log(err) |
| paddy@112 | 197 } |
| paddy@112 | 198 scope, profileID, valid = authCodeGrantValidate(w, req, testContext) |
| paddy@112 | 199 if valid { |
| paddy@112 | 200 t.Fatalf("Expected invalid auth code, got scope `%s` and profileID `%s`.", scope, profileID) |
| paddy@112 | 201 } |
| paddy@112 | 202 if w.Code != http.StatusUnauthorized { |
| paddy@112 | 203 t.Errorf("Expected status %d, got %d", http.StatusUnauthorized, w.Code) |
| paddy@112 | 204 } |
| paddy@112 | 205 expectedBody = `{"error":"invalid_client"}` |
| paddy@112 | 206 if expectedBody != strings.TrimSpace(w.Body.String()) { |
| paddy@112 | 207 t.Errorf("Expected body of `%s`, got `%s`", expectedBody, strings.TrimSpace(w.Body.String())) |
| paddy@112 | 208 } |
| paddy@112 | 209 |
| paddy@112 | 210 req, err = http.NewRequest("POST", "https://test.auth.secondbit.org/oauth2/grant", nil) |
| paddy@112 | 211 if err != nil { |
| paddy@112 | 212 t.Fatal("Can't build request:", err) |
| paddy@112 | 213 } |
| paddy@112 | 214 req.Header.Set("Content-Type", "application/x-www-form-urlencoded") |
| paddy@112 | 215 req.SetBasicAuth(client.ID.String(), client.Secret) |
| paddy@112 | 216 w = httptest.NewRecorder() |
| paddy@112 | 217 params = url.Values{} |
| paddy@112 | 218 params.Set("code", "notmycode") |
| paddy@112 | 219 body = bytes.NewBufferString(params.Encode()) |
| paddy@112 | 220 req.Body = ioutil.NopCloser(body) |
| paddy@112 | 221 err = req.ParseForm() |
| paddy@112 | 222 if err != nil { |
| paddy@112 | 223 t.Log(err) |
| paddy@112 | 224 } |
| paddy@112 | 225 scope, profileID, valid = authCodeGrantValidate(w, req, testContext) |
| paddy@112 | 226 if valid { |
| paddy@112 | 227 t.Fatalf("Expected invalid auth code, got scope `%s` and profileID `%s`.", scope, profileID) |
| paddy@112 | 228 } |
| paddy@112 | 229 if w.Code != http.StatusBadRequest { |
| paddy@112 | 230 t.Errorf("Expected status %d, got %d", http.StatusUnauthorized, w.Code) |
| paddy@112 | 231 } |
| paddy@112 | 232 expectedBody = `{"error":"invalid_grant"}` |
| paddy@112 | 233 if expectedBody != strings.TrimSpace(w.Body.String()) { |
| paddy@112 | 234 t.Errorf("Expected body of `%s`, got `%s`", expectedBody, strings.TrimSpace(w.Body.String())) |
| paddy@112 | 235 } |
| paddy@112 | 236 |
| paddy@112 | 237 req, err = http.NewRequest("POST", "https://test.auth.secondbit.org/oauth2/grant", nil) |
| paddy@112 | 238 if err != nil { |
| paddy@112 | 239 t.Fatal("Can't build request:", err) |
| paddy@112 | 240 } |
| paddy@112 | 241 req.Header.Set("Content-Type", "application/x-www-form-urlencoded") |
| paddy@112 | 242 req.SetBasicAuth(client.ID.String(), client.Secret) |
| paddy@112 | 243 w = httptest.NewRecorder() |
| paddy@112 | 244 params = url.Values{} |
| paddy@112 | 245 params.Set("code", code.Code) |
| paddy@112 | 246 params.Set("redirect_uri", "not my redirectURI") |
| paddy@112 | 247 body = bytes.NewBufferString(params.Encode()) |
| paddy@112 | 248 req.Body = ioutil.NopCloser(body) |
| paddy@112 | 249 err = req.ParseForm() |
| paddy@112 | 250 if err != nil { |
| paddy@112 | 251 t.Log(err) |
| paddy@112 | 252 } |
| paddy@112 | 253 scope, profileID, valid = authCodeGrantValidate(w, req, testContext) |
| paddy@112 | 254 if valid { |
| paddy@112 | 255 t.Fatalf("Expected invalid auth code, got scope `%s` and profileID `%s`.", scope, profileID) |
| paddy@112 | 256 } |
| paddy@112 | 257 if w.Code != http.StatusBadRequest { |
| paddy@112 | 258 t.Errorf("Expected status %d, got %d", http.StatusUnauthorized, w.Code) |
| paddy@112 | 259 } |
| paddy@112 | 260 expectedBody = `{"error":"invalid_grant"}` |
| paddy@112 | 261 if expectedBody != strings.TrimSpace(w.Body.String()) { |
| paddy@112 | 262 t.Errorf("Expected body of `%s`, got `%s`", expectedBody, strings.TrimSpace(w.Body.String())) |
| paddy@112 | 263 } |
| paddy@112 | 264 |
| paddy@112 | 265 req, err = http.NewRequest("POST", "https://test.auth.secondbit.org/oauth2/grant", nil) |
| paddy@112 | 266 if err != nil { |
| paddy@112 | 267 t.Fatal("Can't build request:", err) |
| paddy@112 | 268 } |
| paddy@112 | 269 req.Header.Set("Content-Type", "application/x-www-form-urlencoded") |
| paddy@112 | 270 req.SetBasicAuth(client.ID.String(), client.Secret) |
| paddy@112 | 271 w = httptest.NewRecorder() |
| paddy@112 | 272 params = url.Values{} |
| paddy@112 | 273 params.Set("code", code.Code) |
| paddy@112 | 274 params.Set("redirect_uri", code.RedirectURI) |
| paddy@112 | 275 body = bytes.NewBufferString(params.Encode()) |
| paddy@112 | 276 req.Body = ioutil.NopCloser(body) |
| paddy@112 | 277 err = req.ParseForm() |
| paddy@112 | 278 if err != nil { |
| paddy@112 | 279 t.Log(err) |
| paddy@112 | 280 } |
| paddy@112 | 281 scope, profileID, valid = authCodeGrantValidate(w, req, testContext) |
| paddy@112 | 282 if valid { |
| paddy@112 | 283 t.Fatalf("Expected invalid auth code, got scope `%s` and profileID `%s`.", scope, profileID) |
| paddy@112 | 284 } |
| paddy@112 | 285 if w.Code != http.StatusBadRequest { |
| paddy@112 | 286 t.Errorf("Expected status %d, got %d", http.StatusUnauthorized, w.Code) |
| paddy@112 | 287 } |
| paddy@112 | 288 expectedBody = `{"error":"invalid_grant"}` |
| paddy@112 | 289 if expectedBody != strings.TrimSpace(w.Body.String()) { |
| paddy@112 | 290 t.Errorf("Expected body of `%s`, got `%s`", expectedBody, strings.TrimSpace(w.Body.String())) |
| paddy@112 | 291 } |
| paddy@112 | 292 |
| paddy@112 | 293 req, err = http.NewRequest("POST", "https://test.auth.secondbit.org/oauth2/grant", nil) |
| paddy@112 | 294 if err != nil { |
| paddy@112 | 295 t.Fatal("Can't build request:", err) |
| paddy@112 | 296 } |
| paddy@112 | 297 req.Header.Set("Content-Type", "application/x-www-form-urlencoded") |
| paddy@112 | 298 req.SetBasicAuth(client.ID.String(), client.Secret) |
| paddy@112 | 299 w = httptest.NewRecorder() |
| paddy@112 | 300 params = url.Values{} |
| paddy@112 | 301 params.Set("code", code2.Code) |
| paddy@112 | 302 params.Set("redirect_uri", code2.RedirectURI) |
| paddy@112 | 303 body = bytes.NewBufferString(params.Encode()) |
| paddy@112 | 304 req.Body = ioutil.NopCloser(body) |
| paddy@112 | 305 err = req.ParseForm() |
| paddy@112 | 306 if err != nil { |
| paddy@112 | 307 t.Log(err) |
| paddy@112 | 308 } |
| paddy@112 | 309 scope, profileID, valid = authCodeGrantValidate(w, req, testContext) |
| paddy@112 | 310 if !valid { |
| paddy@112 | 311 t.Fatalf("Expected valid auth code, was not valid.") |
| paddy@111 | 312 } |
| paddy@111 | 313 } |
| paddy@112 | 314 |
| paddy@112 | 315 func TestAuthCodeGrantInvalidate(t *testing.T) { |
| paddy@112 | 316 t.Parallel() |
| paddy@112 | 317 store := NewMemstore() |
| paddy@112 | 318 testContext := Context{ |
| paddy@112 | 319 clients: store, |
| paddy@112 | 320 authCodes: store, |
| paddy@112 | 321 profiles: store, |
| paddy@112 | 322 tokens: store, |
| paddy@112 | 323 sessions: store, |
| paddy@112 | 324 } |
| paddy@112 | 325 code := AuthorizationCode{ |
| paddy@112 | 326 Code: "myauthcode", |
| paddy@112 | 327 Created: time.Now(), |
| paddy@112 | 328 ExpiresIn: 180, |
| paddy@112 | 329 ClientID: uuid.NewID(), |
| paddy@112 | 330 Scope: "scope", |
| paddy@112 | 331 RedirectURI: "redirectURI", |
| paddy@112 | 332 State: "state", |
| paddy@112 | 333 } |
| paddy@112 | 334 err := testContext.SaveAuthorizationCode(code) |
| paddy@112 | 335 if err != nil { |
| paddy@112 | 336 t.Fatal("Can't add auth code:", err) |
| paddy@112 | 337 } |
| paddy@112 | 338 req, err := http.NewRequest("POST", "https://test.auth.secondbit.org/oauth2/grant", nil) |
| paddy@112 | 339 if err != nil { |
| paddy@112 | 340 t.Fatal("Can't build request:", err) |
| paddy@112 | 341 } |
| paddy@112 | 342 err = authCodeGrantInvalidate(req, testContext) |
| paddy@112 | 343 if err != ErrAuthorizationCodeNotFound { |
| paddy@112 | 344 t.Errorf("Expected `%s`, got `%+v`", ErrAuthorizationCodeNotFound, err) |
| paddy@112 | 345 } |
| paddy@112 | 346 req, err = http.NewRequest("POST", "https://test.auth.secondbit.org/oauth2/grant", nil) |
| paddy@112 | 347 if err != nil { |
| paddy@112 | 348 t.Fatal("Can't build request:", err) |
| paddy@112 | 349 } |
| paddy@112 | 350 req.Header.Set("Content-Type", "application/x-www-form-urlencoded") |
| paddy@112 | 351 params := url.Values{} |
| paddy@112 | 352 params.Set("code", "notmycode") |
| paddy@112 | 353 body := bytes.NewBufferString(params.Encode()) |
| paddy@112 | 354 req.Body = ioutil.NopCloser(body) |
| paddy@112 | 355 err = authCodeGrantInvalidate(req, testContext) |
| paddy@112 | 356 if err != ErrAuthorizationCodeNotFound { |
| paddy@112 | 357 t.Errorf("Expected `%s`, got `%+v`", ErrAuthorizationCodeNotFound, err) |
| paddy@112 | 358 } |
| paddy@112 | 359 req, err = http.NewRequest("POST", "https://test.auth.secondbit.org/oauth2/grant", nil) |
| paddy@112 | 360 if err != nil { |
| paddy@112 | 361 t.Fatal("Can't build request:", err) |
| paddy@112 | 362 } |
| paddy@112 | 363 req.Header.Set("Content-Type", "application/x-www-form-urlencoded") |
| paddy@112 | 364 params.Set("code", code.Code) |
| paddy@112 | 365 body = bytes.NewBufferString(params.Encode()) |
| paddy@112 | 366 req.Body = ioutil.NopCloser(body) |
| paddy@112 | 367 err = authCodeGrantInvalidate(req, testContext) |
| paddy@112 | 368 if err != nil { |
| paddy@112 | 369 t.Error("Error invalidating auth code:", err) |
| paddy@112 | 370 } |
| paddy@112 | 371 authCode, err := testContext.GetAuthorizationCode(code.Code) |
| paddy@112 | 372 if err != nil { |
| paddy@112 | 373 t.Error("Error retrieving auth code:", err) |
| paddy@112 | 374 } |
| paddy@112 | 375 if !authCode.Used { |
| paddy@112 | 376 t.Error("Expected auth code to be used, was not.") |
| paddy@112 | 377 } |
| paddy@112 | 378 } |