IndieAuth #25

Merged
h4kor merged 19 commits from auth into master 2022-11-07 19:38:21 +00:00
5 changed files with 54 additions and 29 deletions
Showing only changes of commit 9389cc0266 - Show all commits

View File

@ -75,7 +75,7 @@ func TestAuthPostCorrectPassword(t *testing.T) {
func TestAuthPostWithIncorrectCode(t *testing.T) { func TestAuthPostWithIncorrectCode(t *testing.T) {
repo, user := getSingleUserTestRepo() repo, user := getSingleUserTestRepo()
user.ResetPassword("testpassword") user.ResetPassword("testpassword")
user.GenerateAuthCode("http://example.com", "http://example.com/response", "", "") user.GenerateAuthCode("http://example.com", "http://example.com/response", "", "", "profile")
// Create Request and Response // Create Request and Response
form := url.Values{} form := url.Values{}
@ -97,7 +97,7 @@ func TestAuthPostWithIncorrectCode(t *testing.T) {
func TestAuthPostWithCorrectCode(t *testing.T) { func TestAuthPostWithCorrectCode(t *testing.T) {
repo, user := getSingleUserTestRepo() repo, user := getSingleUserTestRepo()
user.ResetPassword("testpassword") user.ResetPassword("testpassword")
code, _ := user.GenerateAuthCode("http://example.com", "http://example.com/response", "", "") code, _ := user.GenerateAuthCode("http://example.com", "http://example.com/response", "", "", "profile")
// Create Request and Response // Create Request and Response
form := url.Values{} form := url.Values{}
@ -135,7 +135,7 @@ func TestAuthPostWithCorrectCodeAndPKCE(t *testing.T) {
h := sha256.New() h := sha256.New()
h.Write([]byte(code_verifier)) h.Write([]byte(code_verifier))
code_challenge := base64.RawURLEncoding.EncodeToString(h.Sum(nil)) code_challenge := base64.RawURLEncoding.EncodeToString(h.Sum(nil))
code, _ := user.GenerateAuthCode("http://example.com", "http://example.com/response", code_challenge, "S256") code, _ := user.GenerateAuthCode("http://example.com", "http://example.com/response", code_challenge, "S256", "profile")
form := url.Values{} form := url.Values{}
form.Add("code", code) form.Add("code", code)
@ -173,7 +173,7 @@ func TestAuthPostWithCorrectCodeAndWrongPKCE(t *testing.T) {
h := sha256.New() h := sha256.New()
h.Write([]byte(code_verifier + "wrong")) h.Write([]byte(code_verifier + "wrong"))
code_challenge := base64.RawURLEncoding.EncodeToString(h.Sum(nil)) code_challenge := base64.RawURLEncoding.EncodeToString(h.Sum(nil))
code, _ := user.GenerateAuthCode("http://example.com", "http://example.com/response", code_challenge, "S256") code, _ := user.GenerateAuthCode("http://example.com", "http://example.com/response", code_challenge, "S256", "profile")
form := url.Values{} form := url.Values{}
form.Add("code", code) form.Add("code", code)
@ -200,7 +200,7 @@ func TestAuthPostWithCorrectCodePKCEPlain(t *testing.T) {
// Create Request and Response // Create Request and Response
code_verifier := "test_code_verifier" code_verifier := "test_code_verifier"
code_challenge := code_verifier code_challenge := code_verifier
code, _ := user.GenerateAuthCode("http://example.com", "http://example.com/response", code_challenge, "plain") code, _ := user.GenerateAuthCode("http://example.com", "http://example.com/response", code_challenge, "plain", "profile")
form := url.Values{} form := url.Values{}
form.Add("code", code) form.Add("code", code)
@ -227,7 +227,7 @@ func TestAuthPostWithCorrectCodePKCEPlainWrong(t *testing.T) {
// Create Request and Response // Create Request and Response
code_verifier := "test_code_verifier" code_verifier := "test_code_verifier"
code_challenge := code_verifier + "wrong" code_challenge := code_verifier + "wrong"
code, _ := user.GenerateAuthCode("http://example.com", "http://example.com/response", code_challenge, "plain") code, _ := user.GenerateAuthCode("http://example.com", "http://example.com/response", code_challenge, "plain", "profile")
form := url.Values{} form := url.Values{}
form.Add("code", code) form.Add("code", code)
@ -343,7 +343,7 @@ func TestAuthRedirectUriSameHost(t *testing.T) {
func TestAccessTokenCorrectPassword(t *testing.T) { func TestAccessTokenCorrectPassword(t *testing.T) {
repo, user := getSingleUserTestRepo() repo, user := getSingleUserTestRepo()
user.ResetPassword("testpassword") user.ResetPassword("testpassword")
code, _ := user.GenerateAuthCode("http://example.com", "http://example.com/response", "", "") code, _ := user.GenerateAuthCode("http://example.com", "http://example.com/response", "", "", "profile create")
// Create Request and Response // Create Request and Response
form := url.Values{} form := url.Values{}
@ -367,11 +367,13 @@ func TestAccessTokenCorrectPassword(t *testing.T) {
AccessToken string `json:"access_token"` AccessToken string `json:"access_token"`
ExpiresIn int `json:"expires_in"` ExpiresIn int `json:"expires_in"`
RefreshToken string `json:"refresh_token"` RefreshToken string `json:"refresh_token"`
Scope string `json:"scope"`
} }
var response responseType var response responseType
json.Unmarshal(rr.Body.Bytes(), &response) json.Unmarshal(rr.Body.Bytes(), &response)
assertions.AssertEqual(t, response.Me, user.FullUrl()) assertions.AssertEqual(t, response.Me, user.FullUrl())
assertions.AssertEqual(t, response.TokenType, "Bearer") assertions.AssertEqual(t, response.TokenType, "Bearer")
assertions.AssertEqual(t, response.Scope, "profile create")
assertions.Assert(t, response.ExpiresIn > 0, "ExpiresIn should be greater than 0") 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") assertions.Assert(t, len(response.AccessToken) > 0, "AccessToken should be greater than 0")
} }
@ -379,7 +381,7 @@ func TestAccessTokenCorrectPassword(t *testing.T) {
func TestAccessTokenWithIncorrectCode(t *testing.T) { func TestAccessTokenWithIncorrectCode(t *testing.T) {
repo, user := getSingleUserTestRepo() repo, user := getSingleUserTestRepo()
user.ResetPassword("testpassword") user.ResetPassword("testpassword")
user.GenerateAuthCode("http://example.com", "http://example.com/response", "", "") user.GenerateAuthCode("http://example.com", "http://example.com/response", "", "", "profile")
// Create Request and Response // Create Request and Response
form := url.Values{} form := url.Values{}

View File

@ -76,6 +76,7 @@ func userAuthHandler(repo *owl.Repository) func(http.ResponseWriter, *http.Reque
responseType := r.URL.Query().Get("response_type") responseType := r.URL.Query().Get("response_type")
codeChallenge := r.URL.Query().Get("code_challenge") codeChallenge := r.URL.Query().Get("code_challenge")
codeChallengeMethod := r.URL.Query().Get("code_challenge_method") codeChallengeMethod := r.URL.Query().Get("code_challenge_method")
scope := r.URL.Query().Get("scope")
// check if request is valid // check if request is valid
missing_params := []string{} missing_params := []string{}
@ -152,6 +153,7 @@ func userAuthHandler(repo *owl.Repository) func(http.ResponseWriter, *http.Reque
ClientId: clientId, ClientId: clientId,
RedirectUri: redirectUri, RedirectUri: redirectUri,
State: state, State: state,
Scope: scope,
ResponseType: responseType, ResponseType: responseType,
CodeChallenge: codeChallenge, CodeChallenge: codeChallenge,
CodeChallengeMethod: codeChallengeMethod, CodeChallengeMethod: codeChallengeMethod,
@ -171,14 +173,14 @@ func userAuthHandler(repo *owl.Repository) func(http.ResponseWriter, *http.Reque
} }
} }
func verifyAuthCodeRequest(user owl.User, w http.ResponseWriter, r *http.Request) bool { func verifyAuthCodeRequest(user owl.User, w http.ResponseWriter, r *http.Request) (bool, owl.AuthCode) {
// get form data from post request // get form data from post request
err := r.ParseForm() err := r.ParseForm()
if err != nil { if err != nil {
println("Error parsing form: ", err.Error()) println("Error parsing form: ", err.Error())
w.WriteHeader(http.StatusBadRequest) w.WriteHeader(http.StatusBadRequest)
w.Write([]byte("Error parsing form")) w.Write([]byte("Error parsing form"))
return false return false, owl.AuthCode{}
} }
code := r.Form.Get("code") code := r.Form.Get("code")
client_id := r.Form.Get("client_id") client_id := r.Form.Get("client_id")
@ -186,13 +188,12 @@ func verifyAuthCodeRequest(user owl.User, w http.ResponseWriter, r *http.Request
code_verifier := r.Form.Get("code_verifier") code_verifier := r.Form.Get("code_verifier")
// check if request is valid // check if request is valid
valid := user.VerifyAuthCode(code, client_id, redirect_uri, code_verifier) valid, authCode := user.VerifyAuthCode(code, client_id, redirect_uri, code_verifier)
if !valid { if !valid {
w.WriteHeader(http.StatusUnauthorized) w.WriteHeader(http.StatusUnauthorized)
w.Write([]byte("Invalid code")) w.Write([]byte("Invalid code"))
return false
} }
return true return valid, authCode
} }
func userAuthProfileHandler(repo *owl.Repository) func(http.ResponseWriter, *http.Request, httprouter.Params) { func userAuthProfileHandler(repo *owl.Repository) func(http.ResponseWriter, *http.Request, httprouter.Params) {
@ -204,7 +205,8 @@ func userAuthProfileHandler(repo *owl.Repository) func(http.ResponseWriter, *htt
return return
} }
if verifyAuthCodeRequest(user, w, r) { valid, _ := verifyAuthCodeRequest(user, w, r)
if valid {
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
type ResponseProfile struct { type ResponseProfile struct {
Name string `json:"name"` Name string `json:"name"`
@ -244,15 +246,23 @@ func userAuthTokenHandler(repo *owl.Repository) func(http.ResponseWriter, *http.
return return
} }
if verifyAuthCodeRequest(user, w, r) { valid, authCode := verifyAuthCodeRequest(user, w, r)
if valid {
if authCode.Scope == "" {
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte("Empty scope, no token issued"))
return
}
type Response struct { type Response struct {
Me string `json:"me"` Me string `json:"me"`
TokenType string `json:"token_type"` TokenType string `json:"token_type"`
AccessToken string `json:"access_token"` AccessToken string `json:"access_token"`
Scope string `json:"scope"`
ExpiresIn int `json:"expires_in"` ExpiresIn int `json:"expires_in"`
RefreshToken string `json:"refresh_token"` RefreshToken string `json:"refresh_token"`
} }
accessToken, duration, err := user.GenerateAccessToken() accessToken, duration, err := user.GenerateAccessToken(authCode)
if err != nil { if err != nil {
println("Error generating access token: ", err.Error()) println("Error generating access token: ", err.Error())
w.WriteHeader(http.StatusInternalServerError) w.WriteHeader(http.StatusInternalServerError)
@ -263,6 +273,7 @@ func userAuthTokenHandler(repo *owl.Repository) func(http.ResponseWriter, *http.
Me: user.FullUrl(), Me: user.FullUrl(),
TokenType: "Bearer", TokenType: "Bearer",
AccessToken: accessToken, AccessToken: accessToken,
Scope: authCode.Scope,
ExpiresIn: duration, ExpiresIn: duration,
} }
jsonData, err := json.Marshal(response) jsonData, err := json.Marshal(response)
@ -301,6 +312,7 @@ func userAuthVerifyHandler(repo *owl.Repository) func(http.ResponseWriter, *http
state := r.FormValue("state") state := r.FormValue("state")
code_challenge := r.FormValue("code_challenge") code_challenge := r.FormValue("code_challenge")
code_challenge_method := r.FormValue("code_challenge_method") code_challenge_method := r.FormValue("code_challenge_method")
scope := r.FormValue("scope")
// CSRF check // CSRF check
formCsrfToken := r.FormValue("csrf_token") formCsrfToken := r.FormValue("csrf_token")
@ -336,7 +348,7 @@ func userAuthVerifyHandler(repo *owl.Repository) func(http.ResponseWriter, *http
} else { } else {
// password is valid, generate code // password is valid, generate code
code, err := user.GenerateAuthCode( code, err := user.GenerateAuthCode(
client_id, redirect_uri, code_challenge, code_challenge_method) client_id, redirect_uri, code_challenge, code_challenge_method, scope)
if err != nil { if err != nil {
println("Error generating code: ", err.Error()) println("Error generating code: ", err.Error())
w.WriteHeader(http.StatusInternalServerError) w.WriteHeader(http.StatusInternalServerError)

View File

@ -10,5 +10,6 @@
<input type="hidden" name="csrf_token" value="{{.CsrfToken}}"> <input type="hidden" name="csrf_token" value="{{.CsrfToken}}">
<input type="hidden" name="code_challenge" value="{{.CodeChallenge}}"> <input type="hidden" name="code_challenge" value="{{.CodeChallenge}}">
<input type="hidden" name="code_challenge_method" value="{{.CodeChallengeMethod}}"> <input type="hidden" name="code_challenge_method" value="{{.CodeChallengeMethod}}">
<input type="hidden" name="scope" value="{{.Scope}}">
<input type="submit" value="Login"> <input type="submit" value="Login">
</form> </form>

View File

@ -25,6 +25,7 @@ type AuthRequestData struct {
ClientId string ClientId string
RedirectUri string RedirectUri string
State string State string
Scope string
ResponseType string ResponseType string
CodeChallenge string CodeChallenge string
CodeChallengeMethod string CodeChallengeMethod string

21
user.go
View File

@ -39,11 +39,15 @@ type AuthCode struct {
RedirectUri string `yaml:"redirect_uri"` RedirectUri string `yaml:"redirect_uri"`
CodeChallenge string `yaml:"code_challenge"` CodeChallenge string `yaml:"code_challenge"`
CodeChallengeMethod string `yaml:"code_challenge_method"` CodeChallengeMethod string `yaml:"code_challenge_method"`
Scope string `yaml:"scope"`
Created time.Time `yaml:"created"` Created time.Time `yaml:"created"`
} }
type AccessToken struct { type AccessToken struct {
Token string `yaml:"token"` Token string `yaml:"token"`
Scope string `yaml:"scope"`
ClientId string `yaml:"client_id"`
RedirectUri string `yaml:"redirect_uri"`
Created time.Time `yaml:"created"` Created time.Time `yaml:"created"`
ExpiresIn int `yaml:"expires_in"` ExpiresIn int `yaml:"expires_in"`
} }
@ -306,6 +310,7 @@ func (user User) addAuthCode(code AuthCode) error {
func (user User) GenerateAuthCode( func (user User) GenerateAuthCode(
client_id string, redirect_uri string, client_id string, redirect_uri string,
code_challenge string, code_challenge_method string, code_challenge string, code_challenge_method string,
scope string,
) (string, error) { ) (string, error) {
// generate code // generate code
code := GenerateRandomString(32) code := GenerateRandomString(32)
@ -315,28 +320,29 @@ func (user User) GenerateAuthCode(
RedirectUri: redirect_uri, RedirectUri: redirect_uri,
CodeChallenge: code_challenge, CodeChallenge: code_challenge,
CodeChallengeMethod: code_challenge_method, CodeChallengeMethod: code_challenge_method,
Scope: scope,
Created: time.Now(), Created: time.Now(),
}) })
} }
func (user User) VerifyAuthCode( func (user User) VerifyAuthCode(
code string, client_id string, redirect_uri string, code_verifier string, code string, client_id string, redirect_uri string, code_verifier string,
) bool { ) (bool, AuthCode) {
codes := user.getAuthCodes() codes := user.getAuthCodes()
for _, c := range codes { for _, c := range codes {
if c.Code == code && c.ClientId == client_id && c.RedirectUri == redirect_uri { if c.Code == code && c.ClientId == client_id && c.RedirectUri == redirect_uri {
if c.CodeChallengeMethod == "plain" { if c.CodeChallengeMethod == "plain" {
return c.CodeChallenge == code_verifier return c.CodeChallenge == code_verifier, c
} else if c.CodeChallengeMethod == "S256" { } else if c.CodeChallengeMethod == "S256" {
// hash code_verifier // hash code_verifier
hash := sha256.Sum256([]byte(code_verifier)) hash := sha256.Sum256([]byte(code_verifier))
return c.CodeChallenge == base64.RawURLEncoding.EncodeToString(hash[:]) return c.CodeChallenge == base64.RawURLEncoding.EncodeToString(hash[:]), c
} else if c.CodeChallengeMethod == "" { } else if c.CodeChallengeMethod == "" {
return true return true, c
} }
} }
} }
return false return false, AuthCode{}
} }
func (user User) getAccessTokens() []AccessToken { func (user User) getAccessTokens() []AccessToken {
@ -351,12 +357,15 @@ func (user User) addAccessToken(code AccessToken) error {
return saveToYaml(user.AccessTokensFile(), codes) return saveToYaml(user.AccessTokensFile(), codes)
} }
func (user User) GenerateAccessToken() (string, int, error) { func (user User) GenerateAccessToken(authCode AuthCode) (string, int, error) {
// generate code // generate code
token := GenerateRandomString(32) token := GenerateRandomString(32)
duration := 24 * 60 * 60 duration := 24 * 60 * 60
return token, duration, user.addAccessToken(AccessToken{ return token, duration, user.addAccessToken(AccessToken{
Token: token, Token: token,
ClientId: authCode.ClientId,
RedirectUri: authCode.RedirectUri,
Scope: authCode.Scope,
ExpiresIn: duration, ExpiresIn: duration,
Created: time.Now(), Created: time.Now(),
}) })