mirror of
https://github.com/pocket-id/pocket-id.git
synced 2026-05-13 00:19:52 +00:00
add support for response_mode=form_post
This commit is contained in:
@@ -91,6 +91,15 @@ func RegisterFrontend(router *gin.Engine, rateLimitMiddleware gin.HandlerFunc) e
|
||||
}
|
||||
|
||||
if path == "index.html" {
|
||||
// Check if this is an OAuth2 authorization request with response_mode=form_post
|
||||
// In that case, we need to allow form submissions to the redirect_uri
|
||||
responseMode := c.Query("response_mode")
|
||||
redirectURI := c.Query("redirect_uri")
|
||||
if responseMode == "form_post" && redirectURI != "" {
|
||||
// Set the allowed form-action in CSP to include the redirect URI
|
||||
middleware.SetAllowedFormAction(c, redirectURI)
|
||||
}
|
||||
|
||||
nonce := middleware.GetCSPNonce(c)
|
||||
|
||||
// Do not cache the HTML shell, as it embeds a per-request nonce
|
||||
|
||||
@@ -94,6 +94,11 @@ func (oc *OidcController) authorizeHandler(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// Set the allowed form-action in CSP when response_mode is form_post
|
||||
if input.ResponseMode == "form_post" && input.CallbackURL != "" {
|
||||
middleware.SetAllowedFormAction(c, input.CallbackURL)
|
||||
}
|
||||
|
||||
code, callbackURL, err := oc.oidcService.Authorize(c.Request.Context(), input, c.GetString("userID"), c.ClientIP(), c.Request.UserAgent())
|
||||
if err != nil {
|
||||
_ = c.Error(err)
|
||||
|
||||
@@ -71,6 +71,7 @@ type AuthorizeOidcClientRequestDto struct {
|
||||
CodeChallenge string `json:"codeChallenge"`
|
||||
CodeChallengeMethod string `json:"codeChallengeMethod"`
|
||||
ReauthenticationToken string `json:"reauthenticationToken"`
|
||||
ResponseMode string `json:"responseMode"`
|
||||
}
|
||||
|
||||
type AuthorizeOidcClientResponseDto struct {
|
||||
|
||||
@@ -23,17 +23,39 @@ func GetCSPNonce(c *gin.Context) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// SetAllowedFormAction sets the allowed form-action for the CSP header.
|
||||
// This is used for OAuth2 response_mode=form_post to allow form submissions to the client's redirect URI.
|
||||
func SetAllowedFormAction(c *gin.Context, uri string) {
|
||||
c.Set("csp_allowed_form_action", uri)
|
||||
}
|
||||
|
||||
func (m *CspMiddleware) Add() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
// Generate a random base64 nonce for this request
|
||||
nonce := generateNonce()
|
||||
c.Set("csp_nonce", nonce)
|
||||
|
||||
// Get any allowed form-action from context (set by the authorize endpoint)
|
||||
// Also check query parameters for response_mode=form_post (for both frontend and API requests)
|
||||
formAction := "'self'"
|
||||
if v, ok := c.Get("csp_allowed_form_action"); ok {
|
||||
if uri, ok := v.(string); ok && uri != "" {
|
||||
formAction = "'self' " + uri
|
||||
}
|
||||
} else {
|
||||
// If not set by the authorize endpoint, check query parameters
|
||||
responseMode := c.Query("response_mode")
|
||||
redirectURI := c.Query("redirect_uri")
|
||||
if responseMode == "form_post" && redirectURI != "" {
|
||||
formAction = "'self' " + redirectURI
|
||||
}
|
||||
}
|
||||
|
||||
csp := "default-src 'self'; " +
|
||||
"base-uri 'self'; " +
|
||||
"object-src 'none'; " +
|
||||
"frame-ancestors 'none'; " +
|
||||
"form-action 'self'; " +
|
||||
"form-action " + formAction + "; " +
|
||||
"img-src * blob:;" +
|
||||
"font-src 'self'; " +
|
||||
"style-src 'self' 'unsafe-inline'; " +
|
||||
|
||||
@@ -22,7 +22,8 @@ class OidcService extends APIService {
|
||||
nonce?: string,
|
||||
codeChallenge?: string,
|
||||
codeChallengeMethod?: string,
|
||||
reauthenticationToken?: string
|
||||
reauthenticationToken?: string,
|
||||
responseMode?: string
|
||||
) => {
|
||||
const res = await this.api.post('/oidc/authorize', {
|
||||
scope,
|
||||
@@ -31,7 +32,8 @@ class OidcService extends APIService {
|
||||
clientId,
|
||||
codeChallenge,
|
||||
codeChallengeMethod,
|
||||
reauthenticationToken
|
||||
reauthenticationToken,
|
||||
responseMode
|
||||
});
|
||||
|
||||
return res.data as AuthorizeResponse;
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
const oidService = new OidcService();
|
||||
|
||||
let { data }: PageProps = $props();
|
||||
let { client, scope, callbackURL, nonce, codeChallenge, codeChallengeMethod, authorizeState } =
|
||||
let { client, scope, callbackURL, nonce, codeChallenge, codeChallengeMethod, authorizeState, responseMode } =
|
||||
data;
|
||||
|
||||
let isLoading = $state(false);
|
||||
@@ -79,7 +79,8 @@
|
||||
nonce,
|
||||
codeChallenge,
|
||||
codeChallengeMethod,
|
||||
reauthToken
|
||||
reauthToken,
|
||||
responseMode
|
||||
)
|
||||
.then(async ({ code, callbackURL, issuer }) => {
|
||||
onSuccess(code, callbackURL, issuer);
|
||||
@@ -93,12 +94,46 @@
|
||||
function onSuccess(code: string, callbackURL: string, issuer: string) {
|
||||
success = true;
|
||||
setTimeout(() => {
|
||||
const redirectURL = new URL(callbackURL);
|
||||
redirectURL.searchParams.append('code', code);
|
||||
redirectURL.searchParams.append('state', authorizeState);
|
||||
redirectURL.searchParams.append('iss', issuer);
|
||||
if (responseMode === 'form_post') {
|
||||
// Create a hidden form and submit it via POST
|
||||
const form = document.createElement('form');
|
||||
form.method = 'POST';
|
||||
form.action = callbackURL;
|
||||
|
||||
window.location.href = redirectURL.toString();
|
||||
// Add code parameter
|
||||
const codeInput = document.createElement('input');
|
||||
codeInput.type = 'hidden';
|
||||
codeInput.name = 'code';
|
||||
codeInput.value = code;
|
||||
form.appendChild(codeInput);
|
||||
|
||||
// Add state parameter
|
||||
if (authorizeState) {
|
||||
const stateInput = document.createElement('input');
|
||||
stateInput.type = 'hidden';
|
||||
stateInput.name = 'state';
|
||||
stateInput.value = authorizeState;
|
||||
form.appendChild(stateInput);
|
||||
}
|
||||
|
||||
// Add issuer parameter
|
||||
const issInput = document.createElement('input');
|
||||
issInput.type = 'hidden';
|
||||
issInput.name = 'iss';
|
||||
issInput.value = issuer;
|
||||
form.appendChild(issInput);
|
||||
|
||||
document.body.appendChild(form);
|
||||
form.submit();
|
||||
} else {
|
||||
// Default query parameter redirect (response_mode=query or not specified)
|
||||
const redirectURL = new URL(callbackURL);
|
||||
redirectURL.searchParams.append('code', code);
|
||||
redirectURL.searchParams.append('state', authorizeState);
|
||||
redirectURL.searchParams.append('iss', issuer);
|
||||
|
||||
window.location.href = redirectURL.toString();
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -14,6 +14,7 @@ export const load: PageLoad = async ({ url }) => {
|
||||
callbackURL: url.searchParams.get('redirect_uri')!,
|
||||
client,
|
||||
codeChallenge: url.searchParams.get('code_challenge')!,
|
||||
codeChallengeMethod: url.searchParams.get('code_challenge_method')!
|
||||
codeChallengeMethod: url.searchParams.get('code_challenge_method')!,
|
||||
responseMode: url.searchParams.get('response_mode') || 'query'
|
||||
};
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user