mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2025-08-06 02:15:20 +02:00
feat: implement PKCE when acting as oauth2 client (for user login)
Closes #2766
This commit is contained in:
parent
27fa12427c
commit
e1d93950ad
5 changed files with 119 additions and 6 deletions
|
@ -5,6 +5,7 @@ package auth
|
|||
|
||||
import (
|
||||
go_context "context"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
@ -860,13 +861,19 @@ func SignInOAuth(ctx *context.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
if err = authSource.Cfg.(*oauth2.Source).Callout(ctx.Req, ctx.Resp); err != nil {
|
||||
codeChallenge, err := generateCodeChallenge(ctx)
|
||||
if err != nil {
|
||||
ctx.ServerError("SignIn", fmt.Errorf("could not generate code_challenge: %w", err))
|
||||
return
|
||||
}
|
||||
|
||||
if err = authSource.Cfg.(*oauth2.Source).Callout(ctx.Req, ctx.Resp, codeChallenge); err != nil {
|
||||
if strings.Contains(err.Error(), "no provider for ") {
|
||||
if err = oauth2.ResetOAuth2(ctx); err != nil {
|
||||
ctx.ServerError("SignIn", err)
|
||||
return
|
||||
}
|
||||
if err = authSource.Cfg.(*oauth2.Source).Callout(ctx.Req, ctx.Resp); err != nil {
|
||||
if err = authSource.Cfg.(*oauth2.Source).Callout(ctx.Req, ctx.Resp, codeChallenge); err != nil {
|
||||
ctx.ServerError("SignIn", err)
|
||||
}
|
||||
return
|
||||
|
@ -1203,6 +1210,27 @@ func handleOAuth2SignIn(ctx *context.Context, source *auth.Source, u *user_model
|
|||
ctx.Redirect(setting.AppSubURL + "/user/two_factor")
|
||||
}
|
||||
|
||||
// generateCodeChallenge stores a code verifier in the session and returns a S256 code challenge for PKCE
|
||||
func generateCodeChallenge(ctx *context.Context) (codeChallenge string, err error) {
|
||||
codeVerifier, err := util.CryptoRandomString(43) // 256/log2(62) = 256 bits of entropy (each char having log2(62) of randomness)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if err = ctx.Session.Set("CodeVerifier", codeVerifier); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return encodeCodeChallenge(codeVerifier)
|
||||
}
|
||||
|
||||
func encodeCodeChallenge(codeVerifier string) (string, error) {
|
||||
hasher := sha256.New()
|
||||
_, err := io.WriteString(hasher, codeVerifier)
|
||||
codeChallenge := base64.RawURLEncoding.EncodeToString(hasher.Sum(nil))
|
||||
return codeChallenge, err
|
||||
}
|
||||
|
||||
// OAuth2UserLoginCallback attempts to handle the callback from the OAuth2 provider and if successful
|
||||
// login the user
|
||||
func oAuth2UserLoginCallback(ctx *context.Context, authSource *auth.Source, request *http.Request, response http.ResponseWriter) (*user_model.User, goth.User, error) {
|
||||
gothUser, err := oAuth2FetchUser(ctx, authSource, request, response)
|
||||
if err != nil {
|
||||
|
@ -1239,7 +1267,9 @@ func oAuth2FetchUser(ctx *context.Context, authSource *auth.Source, request *htt
|
|||
}
|
||||
|
||||
// Proceed to authenticate through goth.
|
||||
gothUser, err := oauth2Source.Callback(request, response)
|
||||
codeVerifier, _ := ctx.Session.Get("CodeVerifier").(string)
|
||||
_ = ctx.Session.Delete("CodeVerifier")
|
||||
gothUser, err := oauth2Source.Callback(request, response, codeVerifier)
|
||||
if err != nil {
|
||||
if err.Error() == "securecookie: the value is too long" || strings.Contains(err.Error(), "Data too long") {
|
||||
log.Error("OAuth2 Provider %s returned too long a token. Current max: %d. Either increase the [OAuth2] MAX_TOKEN_LENGTH or reduce the information returned from the OAuth2 provider", authSource.Name, setting.OAuth2.MaxTokenLength)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue