// AccountMatchesParam returns an AuthCheck that grants access if paramName is the same // as the account's ID; so, for instance, on a route to /accounts/:accountId, with // a request to /accounts/asdf, the AuthCheck will return true if the account's ID is asdf. // As a special case, account.Nobody and account.Super will never match in this method. func AccountMatchesParam(paramName string) AuthCheck { return func(ctx context.Context, w http.ResponseWriter, r *http.Request) error { var acct account.Account if err := GetAccount(ctx, &acct); err != nil { return err } else if acct.Super() || acct.Nobody() { return ErrAccountIDDoesNotMatch } else if acct.Key(ctx).Encode() != kami.Param(ctx, paramName) { return ErrAccountIDDoesNotMatch } else { return nil } } }
func TestMiddleware(t *testing.T) { var acct account.Account ctx := test.WithConfig(context.Background(), map[string]interface{}{"AuthSecret": "foo"}) // a user is no one (i.e., no Authorization header) w := httptest.NewRecorder() r, _ := http.NewRequest("GET", "/", nil) resultCtx := Middleware(ctx, w, r) err := GetAccount(resultCtx, &acct) if err != nil { t.Errorf("Unexpected error %s", err) } if !acct.Nobody() { t.Errorf("acct should have been the zero value, but got %+v", acct) } // bad jwt w = httptest.NewRecorder() r, _ = http.NewRequest("GET", "/", nil) r.Header.Set("Authorization", "wrong") resultCtx = Middleware(ctx, w, r) err = GetAccount(resultCtx, &acct) if err != InvalidJWTError { t.Errorf("Unexpected error %s", err) } // super (i.e., they passed in the secret itself) w = httptest.NewRecorder() r, _ = http.NewRequest("GET", "/", nil) r.Header.Set("Authorization", "foo") resultCtx = Middleware(ctx, w, r) err = GetAccount(resultCtx, &acct) if err != nil { t.Errorf("Unexpected error %s", err) } if !acct.Super() { t.Errorf("Expected the super account, but got %+v", acct) } // super (i.e., they passed in the secret itself) w = httptest.NewRecorder() r, _ = http.NewRequest("GET", "/", nil) r.Header.Set("Authorization", "foo") resultCtx = Middleware(ctx, w, r) err = GetAccount(resultCtx, &acct) if err != nil { t.Errorf("Unexpected error %s", err) } if !acct.Super() { t.Errorf("Expected the super account, but got %+v", acct) } // test against App Engine dev environment from this point forward realCtx, done, _ := aetest.NewContext() defer done() account.New(realCtx, "*****@*****.**", "foobar") realCtx = test.WithConfig(realCtx, map[string]interface{}{"AuthSecret": "foo"}) // nonexistent account w = httptest.NewRecorder() r, _ = http.NewRequest("GET", "/", nil) r.Header.Set("Authorization", test.JWT(&jws.ClaimSet{Sub: "*****@*****.**"}, "foo")) resultCtx = Middleware(realCtx, w, r) if resultCtx != nil { t.Errorf("Expected Middleware to terminate (i.e. to return nil), but it didn't", err) } // existent account (i.e., the happy path) w = httptest.NewRecorder() r, _ = http.NewRequest("GET", "/", nil) r.Header.Set("Authorization", test.JWT(&jws.ClaimSet{Sub: "*****@*****.**"}, "foo")) resultCtx = Middleware(realCtx, w, r) if resultCtx == nil { t.Errorf("Expected Middleware not to terminate (i.e. to return another context), but it terminated") } else if err = GetAccount(resultCtx, &acct); err != nil { t.Errorf("Expected no error when getting a properly authenticated account, but got %s", err) } else if acct.Email != "*****@*****.**" { t.Errorf("Unexpected account on retrieval: %+v", acct) } }