1
0
Fork 0
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:
oliverpool 2024-04-18 09:55:08 +02:00
parent 27fa12427c
commit e1d93950ad
5 changed files with 119 additions and 6 deletions

View file

@ -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)