Skip to content

Commit

Permalink
CORS Support (#6)
Browse files Browse the repository at this point in the history
  • Loading branch information
spjmurray authored Mar 6, 2024
1 parent e91c2a9 commit a55c17b
Show file tree
Hide file tree
Showing 17 changed files with 578 additions and 371 deletions.
4 changes: 2 additions & 2 deletions charts/identity/Chart.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ description: A Helm chart for deploying Unikorn's IdP

type: application

version: v0.1.6
appVersion: v0.1.6
version: v0.1.7
appVersion: v0.1.7

icon: https://raw.githubusercontent.com/unikorn-cloud/assets/main/images/logos/dark-on-light/icon.png
8 changes: 8 additions & 0 deletions charts/identity/templates/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,14 @@ spec:
args:
- --namespace={{ .Release.Namespace }}
- --host=https://{{ .Values.host }}
{{- with $cors := .Values.cors }}
{{- range $origin := $cors.allowOrigin }}
{{ printf "- --cors-allow-origin=%s" $origin | nindent 8 }}
{{- end }}
{{- if $cors.maxAge }}
{{ printf "- --cors-max-age=%s" $cors.maxAge | nindent 8 }}
{{- end }}
{{- end }}
volumeMounts:
- name: unikorn-identity-jose-tls
mountPath: /var/lib/secrets/unikorn-cloud.org/jose
Expand Down
7 changes: 7 additions & 0 deletions charts/identity/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -82,3 +82,10 @@ ingress:

# If true, will add the external DNS hostname annotation.
externalDns: false

# Allows CORS to be configured/secured
# cors:
# # Broswers must send requests from these origin servers, defaults to * if not set.
# allowOrigin: ['*']
# # How long to cache the CORS preflight for, mostly useless as browsers override this.
# maxAge: 86400
12 changes: 9 additions & 3 deletions openapi/server.spec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,12 @@ paths:
responses:
'200':
$ref: '#/components/responses/authorizationResponse'
# TODO: returns HTML not JSON
'302':
description: A redirect back to the validated callback URL.
'400':
$ref: '#/components/responses/badRequestResponse'
$ref: '#/components/responses/htmlErrorResponse'
'500':
$ref: '#/components/responses/internalServerErrorResponse'
$ref: '#/components/responses/htmlErrorResponse'
/oauth2/v2/token:
description: |-
Implements OAuth2 code exchange.
Expand Down Expand Up @@ -603,6 +604,11 @@ components:
example:
error: conflict
error_description: a resource with the same name already exists
htmlErrorResponse:
description: |-
A generic HTML error page.
content:
text/html: {}
internalServerErrorResponse:
description: |-
An unexpected error occurred, this may be an unexpected transient error and
Expand Down
19 changes: 0 additions & 19 deletions pkg/generated/client.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

221 changes: 111 additions & 110 deletions pkg/generated/schema.go

Large diffs are not rendered by default.

10 changes: 0 additions & 10 deletions pkg/handler/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,13 +62,7 @@ func (h *Handler) setUncacheable(w http.ResponseWriter) {
w.Header().Add("Cache-Control", "no-cache")
}

func (h *Handler) setCORS(w http.ResponseWriter) {
w.Header().Add("Access-Control-Allow-Origin", "*")
}

func (h *Handler) GetWellKnownOpenidConfiguration(w http.ResponseWriter, r *http.Request) {
h.setCORS(w)

result := &generated.OpenidConfiguration{
Issuer: h.options.Host,
AuthorizationEndpoint: fmt.Sprintf("%s/oauth2/v2/authorization", h.options.Host),
Expand Down Expand Up @@ -122,8 +116,6 @@ func (h *Handler) PostOauth2V2Login(w http.ResponseWriter, r *http.Request) {
}

func (h *Handler) PostOauth2V2Token(w http.ResponseWriter, r *http.Request) {
h.setCORS(w)

result, err := h.authenticator.OAuth2.Token(w, r)
if err != nil {
errors.HandleError(w, r, err)
Expand All @@ -135,8 +127,6 @@ func (h *Handler) PostOauth2V2Token(w http.ResponseWriter, r *http.Request) {
}

func (h *Handler) GetOauth2V2Jwks(w http.ResponseWriter, r *http.Request) {
h.setCORS(w)

result, err := h.authenticator.JWKS()
if err != nil {
errors.HandleError(w, r, err)
Expand Down
90 changes: 90 additions & 0 deletions pkg/middleware/cors/cors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
/*
Copyright 2024 the Unikorn Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package cors

import (
"net/http"
"strconv"
"strings"

"github.com/spf13/pflag"

"github.com/unikorn-cloud/core/pkg/util"
"github.com/unikorn-cloud/identity/pkg/errors"
"github.com/unikorn-cloud/identity/pkg/middleware/openapi"
)

type Options struct {
AllowedOrigins []string
MaxAge int
}

func (o *Options) AddFlags(f *pflag.FlagSet) {
f.StringSliceVar(&o.AllowedOrigins, "--cors-allow-origin", []string{"*"}, "CORS allowed origins")
f.IntVar(&o.MaxAge, "--cors-max-age", 86400, "CORS maximum age (may be overridden by the browser)")
}

func Middleware(schema *openapi.Schema, options *Options) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// All requests get the allow origin header.
for _, origin := range options.AllowedOrigins {
w.Header().Add("Access-Control-Allow-Origin", origin)
}

// For normal requests handle them.
if r.Method != http.MethodOptions {
next.ServeHTTP(w, r)
return
}

// Handle preflight
method := r.Header.Get("Access-Control-Request-Method")
if method == "" {
errors.HandleError(w, r, errors.OAuth2InvalidRequest("OPTIONS missing Access-Control-Request-Method header"))
return
}

request := r.Clone(r.Context())
request.Method = method

route, _, err := schema.FindRoute(request)
if err != nil {
errors.HandleError(w, r, err)
return
}

// TODO: add OPTIONS to the schema?
methods := util.Keys(route.PathItem.Operations())
methods = append(methods, http.MethodOptions)

// TODO: I've tried adding them to the schema, but the generator
// adds them to the hander function signatures, which is superfluous
// to requirements.
headers := []string{
"Authorization",
"traceparent",
"tracestate",
}

w.Header().Add("Access-Control-Allow-Methods", strings.Join(methods, ", "))
w.Header().Add("Access-Control-Allow-Headers", strings.Join(headers, ", "))
w.Header().Add("Access-Control-Max-Age", strconv.Itoa(options.MaxAge))
w.WriteHeader(http.StatusNoContent)
})
}
}
178 changes: 0 additions & 178 deletions pkg/middleware/logging.go

This file was deleted.

Loading

0 comments on commit a55c17b

Please sign in to comment.