func Auth(w http.ResponseWriter, r *http.Request, c router.Context) error { db, ok := c.Meta["db"].(*sqlx.DB) if !ok { return errors.New("db not set in context") } tokenSecret, ok := c.Meta["tokenSecret"].([]byte) if !ok { return errors.New("token secret not set in context") } // parse the token param token, err := jwt.ParseFromRequest(r, func(token *jwt.Token) (interface{}, error) { return tokenSecret, nil }) if err != nil { return res.Unauthorized(w, res.ErrorMsg{"invalid_token", err.Error()}) } // check if the token is eligible for current scope // scope := scopeRegex.FindStringSubmatch(r.URL.Path)[1] // scopes := token.Claims["scopes"].(string) // // if !contains(strings.Split(scopes, ","), scope) { // return res.Forbidden(w, res.ErrorMsg{"invalid_scope", "token is not valid for this scope"}) // } // check if the token was revoked from DB t := data.Token{} err = t.Get(db, int64(token.Claims["jti"].(float64))) if err != nil { if _, ok := err.(*data.Error); ok { return res.Unauthorized(w, res.ErrorMsg{"invalid_token", "token is not valid"}) } return err } if t.RevokedAt != nil { return res.Unauthorized(w, res.ErrorMsg{"invalid_token", "token is not valid"}) } // valid token // set the user id to context and pass to next handler c.Meta["user_id"] = t.UserID return c.Next(w, r, c) }
func TestAddHub(t *testing.T) { // setup DB db := testhelpers.SetupDB(t) defer db.Close() // setup server ts, err := setupServerHub(db, []byte("secret")) if err != nil { t.Fatal(err) } defer ts.Close() // create a user u := &data.User{ Username: "******", Email: "*****@*****.**", } if err := u.EncryptPassword("password"); err != nil { t.Fatal(err) } if err = u.Insert(db); err != nil { t.Fatal(err) } // create a token for the user tok := data.Token{ UserID: u.ID, ExpiresIn: (30 * 24 * time.Hour).Nanoseconds(), // 30 days } if err := tok.Insert(db); err != nil { t.Fatal(err) } // get the encoded JSON Web Token jwt, err := tok.EncodeJWT([]byte("secret")) if err != nil { t.Fatal(err) } hub := data.Hub{ Slug: "1234", UserID: u.ID, } if err := hub.Insert(db); err != nil { t.Fatal(err) } type testCase struct { path string statusCode int body string } // test when valid params are provided spath := "?slug=abcd&access_token=" + jwt res, err := http.Get(ts.URL + "/api/v0/hub" + spath) if err != nil { t.Fatal(err) } if res.StatusCode != http.StatusOK { t.Errorf("%s - Expected status code %v, Got %v", spath, http.StatusOK, res.StatusCode) } b, err := ioutil.ReadAll(res.Body) res.Body.Close() if err != nil { t.Fatal(err) } h := data.Hub{} if err := json.Unmarshal(b, &h); err != nil { t.Errorf("%s - Expected response body to be %+v, Got %s", spath, h, b) } tCases := []testCase{ // when slug param is missing {"?access_token=" + jwt, http.StatusBadRequest, `{"error":"invalid_request","error_description":"slug required"}`}, // when access_token param is missing {"?slug=abcd", http.StatusUnauthorized, `{"error":"invalid_token","error_description":"no token present in request"}`}, // when trying to add existing hub {"?slug=1234&access_token=" + jwt, http.StatusBadRequest, `{"error":"unique_violation","error_description":"hub exists"}`}, } for _, tc := range tCases { res, err := http.Get(ts.URL + "/api/v0/hub" + tc.path) if err != nil { t.Fatal(err) } if res.StatusCode != tc.statusCode { t.Errorf("%s - Expected status code %v, Got %v", tc.path, tc.statusCode, res.StatusCode) } b, err := ioutil.ReadAll(res.Body) res.Body.Close() if err != nil { t.Fatal(err) } if body := string(b); body != tc.body { t.Errorf("%s - Expected response body to be %v, Got %v", tc.path, tc.body, body) } } }
func TestShowHub(t *testing.T) { // setup DB db := testhelpers.SetupDB(t) defer db.Close() // setup server ts, err := setupServerHub(db, []byte("secret")) if err != nil { t.Fatal(err) } defer ts.Close() // create a user u := &data.User{ Username: "******", Email: "*****@*****.**", } if err := u.EncryptPassword("password"); err != nil { t.Fatal(err) } if err = u.Insert(db); err != nil { t.Fatal(err) } // create a token for the user tok := data.Token{ UserID: u.ID, ExpiresIn: (30 * 24 * time.Hour).Nanoseconds(), // 30 days } if err := tok.Insert(db); err != nil { t.Fatal(err) } // get the encoded JSON Web Token jwt, err := tok.EncodeJWT([]byte("secret")) if err != nil { t.Fatal(err) } hub := data.Hub{ Slug: "abcd", UserID: u.ID, } if err := hub.Insert(db); err != nil { t.Fatal(err) } type testCase struct { path string statusCode int body string } tCases := []testCase{ // when valid params are provided {"?access_token=" + jwt, http.StatusOK, `{"hub":["abcd"]}`}, // when access_token param is missing {"?" + jwt, http.StatusUnauthorized, `{"error":"invalid_token","error_description":"no token present in request"}`}, } for _, tc := range tCases { res, err := http.Post(ts.URL+"/api/v0/hub"+tc.path, "", nil) if err != nil { t.Fatal(err) } if res.StatusCode != tc.statusCode { t.Errorf("%s - Expected status code %v, Got %v", tc.path, tc.statusCode, res.StatusCode) } b, err := ioutil.ReadAll(res.Body) res.Body.Close() if err != nil { t.Fatal(err) } if body := string(b); body != tc.body { t.Errorf("%s - Expected response body to be %v, Got %v", tc.path, tc.body, body) } } }
func TestAuthToken(t *testing.T) { // setup DB db := testhelpers.SetupDB(t) defer db.Close() // setup server ts, err := setupServer(db, []byte("secret")) if err != nil { t.Fatal(err) } defer ts.Close() // create a user u := &data.User{ Username: "******", Email: "*****@*****.**", } if err := u.EncryptPassword("password"); err != nil { t.Fatal(err) } if err = u.Insert(db); err != nil { t.Fatal(err) } // create a token for the user tok := data.Token{ UserID: u.ID, ExpiresIn: (30 * 24 * time.Hour).Nanoseconds(), // 30 days } if err := tok.Insert(db); err != nil { t.Fatal(err) } // get the encoded JSON Web Token jwt, err := tok.EncodeJWT([]byte("secret")) if err != nil { t.Fatal(err) } type testCase struct { path string statusCode int body string } tCases := []testCase{ // when access token not provided {"hub", http.StatusUnauthorized, `{"error":"invalid_token","error_description":"no token present in request"}`}, // when access token is invalid {"hub?access_token=invalid", http.StatusUnauthorized, `{"error":"invalid_token","error_description":"token contains an invalid number of segments"}`}, // // when access token is not properly scoped // // fixme currently valid scopes are ["user", "hub", "app"] // {"admin?access_token=" + jwt, http.statusforbidden, `{"error":"invalid_scope","error_description":"token is not valid for this scope"}`}, // when a valid token is provided {"hub?access_token=" + jwt, http.StatusOK, `{"status":"ok"}`}, // when access token is revoked // TODO } for _, tc := range tCases { res, err := http.Get(ts.URL + path.Join("/api/v0", tc.path)) if err != nil { t.Fatal(err) } if res.StatusCode != tc.statusCode { t.Errorf("%s - Expected status code %v, Got %v", tc.path, tc.statusCode, res.StatusCode) } b, err := ioutil.ReadAll(res.Body) res.Body.Close() if err != nil { t.Fatal(err) } if body := string(b); body != tc.body { t.Errorf("%s - Expected response body to be %v, Got %v", tc.path, tc.body, body) } } }
func TestUserToken(t *testing.T) { // setup DB db := testhelpers.SetupDB(t) defer db.Close() // setup server ts, err := setupServerUser(db, []byte("secret")) if err != nil { t.Fatal(err) } defer ts.Close() // create a user u := &data.User{ Username: "******", Email: "*****@*****.**", } if err := u.EncryptPassword("password"); err != nil { t.Fatal(err) } if err = u.Insert(db); err != nil { t.Fatal(err) } tok := data.Token{ UserID: u.ID, ExpiresIn: (30 * 24 * time.Hour).Nanoseconds(), // 30 days } if err := tok.Insert(db); err != nil { t.Fatal(err) } // // get the encoded JSON Web Token // jwt, err := tok.EncodeJWT([]byte("secret")) // if err != nil { // t.Fatal(err) // } type testCase struct { path string statusCode int body string } tCases := []testCase{ // when valid params are provided // FIXME: find out why signature in jwt is different from response // {"?grant_type=password&login=foo&password=password", http.StatusOK, `{"access_token":` + jwt + `","token_type":"bearer","expires_in":"720h0m0s"}`}, // when grant_type param is invalid/missing {"?login=foo&password=password", http.StatusBadRequest, `{"error":"unsupported_grant_type","error_description":"supports only password grant type"}`}, // when login param is missing {"?grant_type=password&password=password", http.StatusBadRequest, `{"error":"invalid_request","error_description":"login required"}`}, // when password param is missing {"?grant_type=password&login=foo", http.StatusBadRequest, `{"error":"invalid_request","error_description":"password required"}`}, // when password value is incorrect {"?grant_type=password&login=foo&password=abcd", http.StatusBadRequest, `{"error":"invalid_grant","error_description":"failed to authenticate user"}`}, // when login value is incorrect {"?grant_type=password&login=bar&password=password", http.StatusBadRequest, `{"error":"invalid_grant","error_description":"user not found"}`}, } for _, tc := range tCases { res, err := http.Post(ts.URL+"/oauth/token"+tc.path, "", nil) if err != nil { t.Fatal(err) } if res.StatusCode != tc.statusCode { t.Errorf("%s - Expected status code %v, Got %v", tc.path, tc.statusCode, res.StatusCode) } b, err := ioutil.ReadAll(res.Body) res.Body.Close() if err != nil { t.Fatal(err) } if body := string(b); body != tc.body { t.Errorf("%s - Expected response body to be %v, Got %v", tc.path, tc.body, body) } } }
// POST /oauth/token // Params: grant_type, login, password // Requires a tokenSecret to be set in context func UserToken(w http.ResponseWriter, r *http.Request, c router.Context) error { db, ok := c.Meta["db"].(*sqlx.DB) if !ok { return errors.New("db not set in context") } tokenSecret, ok := c.Meta["tokenSecret"].([]byte) if !ok { return errors.New("token secret not set in context") } if r.FormValue("grant_type") != "password" { return res.BadRequest(w, res.ErrorMsg{"unsupported_grant_type", "supports only password grant type"}) } login := r.FormValue("login") if login == "" { return res.BadRequest(w, res.ErrorMsg{"invalid_request", "login required"}) } password := r.FormValue("password") if password == "" { return res.BadRequest(w, res.ErrorMsg{"invalid_request", "password required"}) } u := data.User{} if err := u.GetByLogin(db, login); err != nil { if e, ok := err.(*data.Error); ok { return res.BadRequest(w, res.ErrorMsg{"invalid_grant", e.Desc}) } return err } if !u.VerifyPassword(password) { return res.BadRequest(w, res.ErrorMsg{"invalid_grant", "failed to authenticate user"}) } // Since all is well, generate token and add to database t := data.Token{ UserID: u.ID, ExpiresIn: (30 * 24 * time.Hour).Nanoseconds(), // 30 days } if err := t.Insert(db); err != nil { return err } // get the encoded JSON Web token jwt, err := t.EncodeJWT(tokenSecret) if err != nil { return err } // prepare oAuth2 access token payload payload := struct { AccessToken string `json:"access_token"` TokenType string `json:"token_type"` ExpiresIn string `json:"expires_in"` }{ jwt, "bearer", time.Duration(t.ExpiresIn).String(), } return res.OK(w, payload) }