WIP access token
This commit is contained in:
parent
fa30d4fd8e
commit
ff563c6f05
|
@ -339,3 +339,61 @@ func TestAuthRedirectUriSameHost(t *testing.T) {
|
||||||
|
|
||||||
assertions.AssertStatus(t, rr, http.StatusOK)
|
assertions.AssertStatus(t, rr, http.StatusOK)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAccessTokenCorrectPassword(t *testing.T) {
|
||||||
|
repo, user := getSingleUserTestRepo()
|
||||||
|
user.ResetPassword("testpassword")
|
||||||
|
code, _ := user.GenerateAuthCode("http://example.com", "http://example.com/response", "", "")
|
||||||
|
|
||||||
|
// Create Request and Response
|
||||||
|
form := url.Values{}
|
||||||
|
form.Add("code", code)
|
||||||
|
form.Add("client_id", "http://example.com")
|
||||||
|
form.Add("redirect_uri", "http://example.com/response")
|
||||||
|
form.Add("grant_type", "authorization_code")
|
||||||
|
req, err := http.NewRequest("POST", user.AuthUrl()+"token/", strings.NewReader(form.Encode()))
|
||||||
|
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
|
||||||
|
req.Header.Add("Content-Length", strconv.Itoa(len(form.Encode())))
|
||||||
|
assertions.AssertNoError(t, err, "Error creating request")
|
||||||
|
rr := httptest.NewRecorder()
|
||||||
|
router := main.SingleUserRouter(&repo)
|
||||||
|
router.ServeHTTP(rr, req)
|
||||||
|
|
||||||
|
assertions.AssertStatus(t, rr, http.StatusOK)
|
||||||
|
// parse response as json
|
||||||
|
type responseType struct {
|
||||||
|
Me string `json:"me"`
|
||||||
|
TokenType string `json:"token_type"`
|
||||||
|
AccessToken string `json:"access_token"`
|
||||||
|
ExpiresIn int `json:"expires_in"`
|
||||||
|
RefreshToken string `json:"refresh_token"`
|
||||||
|
}
|
||||||
|
var response responseType
|
||||||
|
json.Unmarshal(rr.Body.Bytes(), &response)
|
||||||
|
assertions.AssertEqual(t, response.Me, user.FullUrl())
|
||||||
|
assertions.AssertEqual(t, response.TokenType, "Bearer")
|
||||||
|
assertions.Assert(t, response.ExpiresIn > 0, "ExpiresIn should be greater than 0")
|
||||||
|
assertions.Assert(t, len(response.AccessToken) > 0, "AccessToken should be greater than 0")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAccessTokenWithIncorrectCode(t *testing.T) {
|
||||||
|
repo, user := getSingleUserTestRepo()
|
||||||
|
user.ResetPassword("testpassword")
|
||||||
|
user.GenerateAuthCode("http://example.com", "http://example.com/response", "", "")
|
||||||
|
|
||||||
|
// Create Request and Response
|
||||||
|
form := url.Values{}
|
||||||
|
form.Add("code", "wrongcode")
|
||||||
|
form.Add("client_id", "http://example.com")
|
||||||
|
form.Add("redirect_uri", "http://example.com/response")
|
||||||
|
form.Add("grant_type", "authorization_code")
|
||||||
|
req, err := http.NewRequest("POST", user.AuthUrl()+"token/", strings.NewReader(form.Encode()))
|
||||||
|
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
|
||||||
|
req.Header.Add("Content-Length", strconv.Itoa(len(form.Encode())))
|
||||||
|
assertions.AssertNoError(t, err, "Error creating request")
|
||||||
|
rr := httptest.NewRecorder()
|
||||||
|
router := main.SingleUserRouter(&repo)
|
||||||
|
router.ServeHTTP(rr, req)
|
||||||
|
|
||||||
|
assertions.AssertStatus(t, rr, http.StatusUnauthorized)
|
||||||
|
}
|
||||||
|
|
|
@ -171,6 +171,30 @@ func userAuthHandler(repo *owl.Repository) func(http.ResponseWriter, *http.Reque
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func verifyAuthCodeRequest(user owl.User, w http.ResponseWriter, r *http.Request) bool {
|
||||||
|
// get form data from post request
|
||||||
|
err := r.ParseForm()
|
||||||
|
if err != nil {
|
||||||
|
println("Error parsing form: ", err.Error())
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
w.Write([]byte("Error parsing form"))
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
code := r.Form.Get("code")
|
||||||
|
client_id := r.Form.Get("client_id")
|
||||||
|
redirect_uri := r.Form.Get("redirect_uri")
|
||||||
|
code_verifier := r.Form.Get("code_verifier")
|
||||||
|
|
||||||
|
// check if request is valid
|
||||||
|
valid := user.VerifyAuthCode(code, client_id, redirect_uri, code_verifier)
|
||||||
|
if !valid {
|
||||||
|
w.WriteHeader(http.StatusUnauthorized)
|
||||||
|
w.Write([]byte("Invalid code"))
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func userAuthProfileHandler(repo *owl.Repository) func(http.ResponseWriter, *http.Request, httprouter.Params) {
|
func userAuthProfileHandler(repo *owl.Repository) func(http.ResponseWriter, *http.Request, httprouter.Params) {
|
||||||
return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
||||||
user, err := getUserFromRepo(repo, ps)
|
user, err := getUserFromRepo(repo, ps)
|
||||||
|
@ -180,26 +204,7 @@ func userAuthProfileHandler(repo *owl.Repository) func(http.ResponseWriter, *htt
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// get form data from post request
|
if verifyAuthCodeRequest(user, w, r) {
|
||||||
err = r.ParseForm()
|
|
||||||
if err != nil {
|
|
||||||
println("Error parsing form: ", err.Error())
|
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
|
||||||
w.Write([]byte("Error parsing form"))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
code := r.Form.Get("code")
|
|
||||||
client_id := r.Form.Get("client_id")
|
|
||||||
redirect_uri := r.Form.Get("redirect_uri")
|
|
||||||
code_verifier := r.Form.Get("code_verifier")
|
|
||||||
|
|
||||||
// check if request is valid
|
|
||||||
valid := user.VerifyAuthCode(code, client_id, redirect_uri, code_verifier)
|
|
||||||
if !valid {
|
|
||||||
w.WriteHeader(http.StatusUnauthorized)
|
|
||||||
w.Write([]byte("Invalid code"))
|
|
||||||
return
|
|
||||||
} else {
|
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
type ResponseProfile struct {
|
type ResponseProfile struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
|
@ -227,7 +232,48 @@ func userAuthProfileHandler(repo *owl.Repository) func(http.ResponseWriter, *htt
|
||||||
w.Write(jsonData)
|
w.Write(jsonData)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func userAuthTokenHandler(repo *owl.Repository) func(http.ResponseWriter, *http.Request, httprouter.Params) {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
||||||
|
user, err := getUserFromRepo(repo, ps)
|
||||||
|
if err != nil {
|
||||||
|
println("Error getting user: ", err.Error())
|
||||||
|
notFoundHandler(repo)(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if verifyAuthCodeRequest(user, w, r) {
|
||||||
|
type Response struct {
|
||||||
|
Me string `json:"me"`
|
||||||
|
TokenType string `json:"token_type"`
|
||||||
|
AccessToken string `json:"access_token"`
|
||||||
|
ExpiresIn int `json:"expires_in"`
|
||||||
|
RefreshToken string `json:"refresh_token"`
|
||||||
|
}
|
||||||
|
accessToken, duration, err := user.GenerateAccessToken()
|
||||||
|
if err != nil {
|
||||||
|
println("Error generating access token: ", err.Error())
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
w.Write([]byte("Internal server error"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response := Response{
|
||||||
|
Me: user.FullUrl(),
|
||||||
|
TokenType: "Bearer",
|
||||||
|
AccessToken: accessToken,
|
||||||
|
ExpiresIn: duration,
|
||||||
|
}
|
||||||
|
jsonData, err := json.Marshal(response)
|
||||||
|
if err != nil {
|
||||||
|
println("Error marshalling json: ", err.Error())
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
w.Write([]byte("Internal server error"))
|
||||||
|
}
|
||||||
|
w.Write(jsonData)
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,7 @@ func Router(repo *owl.Repository) http.Handler {
|
||||||
router.GET("/user/:user/auth/", userAuthHandler(repo))
|
router.GET("/user/:user/auth/", userAuthHandler(repo))
|
||||||
router.POST("/user/:user/auth/", userAuthProfileHandler(repo))
|
router.POST("/user/:user/auth/", userAuthProfileHandler(repo))
|
||||||
router.POST("/user/:user/auth/verify/", userAuthVerifyHandler(repo))
|
router.POST("/user/:user/auth/verify/", userAuthVerifyHandler(repo))
|
||||||
|
router.POST("/user/:user/auth/token/", userAuthTokenHandler(repo))
|
||||||
router.GET("/user/:user/media/*filepath", userMediaHandler(repo))
|
router.GET("/user/:user/media/*filepath", userMediaHandler(repo))
|
||||||
router.GET("/user/:user/index.xml", userRSSHandler(repo))
|
router.GET("/user/:user/index.xml", userRSSHandler(repo))
|
||||||
router.GET("/user/:user/posts/:post/", postHandler(repo))
|
router.GET("/user/:user/posts/:post/", postHandler(repo))
|
||||||
|
@ -33,6 +34,7 @@ func SingleUserRouter(repo *owl.Repository) http.Handler {
|
||||||
router.GET("/auth/", userAuthHandler(repo))
|
router.GET("/auth/", userAuthHandler(repo))
|
||||||
router.POST("/auth/", userAuthProfileHandler(repo))
|
router.POST("/auth/", userAuthProfileHandler(repo))
|
||||||
router.POST("/auth/verify/", userAuthVerifyHandler(repo))
|
router.POST("/auth/verify/", userAuthVerifyHandler(repo))
|
||||||
|
router.POST("/auth/token/", userAuthTokenHandler(repo))
|
||||||
router.GET("/media/*filepath", userMediaHandler(repo))
|
router.GET("/media/*filepath", userMediaHandler(repo))
|
||||||
router.GET("/index.xml", userRSSHandler(repo))
|
router.GET("/index.xml", userRSSHandler(repo))
|
||||||
router.GET("/posts/:post/", postHandler(repo))
|
router.GET("/posts/:post/", postHandler(repo))
|
||||||
|
|
|
@ -29,6 +29,7 @@
|
||||||
<link rel="webmention" href="{{ .User.WebmentionUrl }}">
|
<link rel="webmention" href="{{ .User.WebmentionUrl }}">
|
||||||
{{ if .User.AuthUrl }}
|
{{ if .User.AuthUrl }}
|
||||||
<link rel="authorization_endpoint" href="{{ .User.AuthUrl}}">
|
<link rel="authorization_endpoint" href="{{ .User.AuthUrl}}">
|
||||||
|
<link rel="token_endpoint" href="{{ .User.TokenUrl}}">
|
||||||
{{ end }}
|
{{ end }}
|
||||||
<style>
|
<style>
|
||||||
header {
|
header {
|
||||||
|
|
38
user.go
38
user.go
|
@ -42,6 +42,12 @@ type AuthCode struct {
|
||||||
Created time.Time `yaml:"created"`
|
Created time.Time `yaml:"created"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type AccessToken struct {
|
||||||
|
Token string `yaml:"token"`
|
||||||
|
Created time.Time `yaml:"created"`
|
||||||
|
ExpiresIn int `yaml:"expires_in"`
|
||||||
|
}
|
||||||
|
|
||||||
func (user User) Dir() string {
|
func (user User) Dir() string {
|
||||||
return path.Join(user.repo.UsersDir(), user.name)
|
return path.Join(user.repo.UsersDir(), user.name)
|
||||||
}
|
}
|
||||||
|
@ -63,6 +69,11 @@ func (user User) AuthUrl() string {
|
||||||
return url
|
return url
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (user User) TokenUrl() string {
|
||||||
|
url, _ := url.JoinPath(user.AuthUrl(), "token/")
|
||||||
|
return url
|
||||||
|
}
|
||||||
|
|
||||||
func (user User) WebmentionUrl() string {
|
func (user User) WebmentionUrl() string {
|
||||||
url, _ := url.JoinPath(user.FullUrl(), "webmention/")
|
url, _ := url.JoinPath(user.FullUrl(), "webmention/")
|
||||||
return url
|
return url
|
||||||
|
@ -93,6 +104,10 @@ func (user User) AuthCodesFile() string {
|
||||||
return path.Join(user.MetaDir(), "auth_codes.yml")
|
return path.Join(user.MetaDir(), "auth_codes.yml")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (user User) AccessTokensFile() string {
|
||||||
|
return path.Join(user.MetaDir(), "access_tokens.yml")
|
||||||
|
}
|
||||||
|
|
||||||
func (user User) Name() string {
|
func (user User) Name() string {
|
||||||
return user.name
|
return user.name
|
||||||
}
|
}
|
||||||
|
@ -323,3 +338,26 @@ func (user User) VerifyAuthCode(
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (user User) getAccessTokens() []AccessToken {
|
||||||
|
codes := make([]AccessToken, 0)
|
||||||
|
loadFromYaml(user.AccessTokensFile(), &codes)
|
||||||
|
return codes
|
||||||
|
}
|
||||||
|
|
||||||
|
func (user User) addAccessToken(code AccessToken) error {
|
||||||
|
codes := user.getAccessTokens()
|
||||||
|
codes = append(codes, code)
|
||||||
|
return saveToYaml(user.AccessTokensFile(), codes)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (user User) GenerateAccessToken() (string, int, error) {
|
||||||
|
// generate code
|
||||||
|
token := GenerateRandomString(32)
|
||||||
|
duration := 24 * 60 * 60
|
||||||
|
return token, duration, user.addAccessToken(AccessToken{
|
||||||
|
Token: token,
|
||||||
|
ExpiresIn: duration,
|
||||||
|
Created: time.Now(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue