IndieAuth #25
|
@ -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{}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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>
|
|
@ -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
21
user.go
|
@ -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(),
|
||||||
})
|
})
|
||||||
|
|
Loading…
Reference in New Issue