Skip to content
Snippets Groups Projects
Commit 97b80eed authored by Jakob Probst's avatar Jakob Probst
Browse files

feat: add the ability to skip the approval

parent 9d9698a1
No related branches found
No related tags found
No related merge requests found
...@@ -11,7 +11,7 @@ trim_trailing_whitespace = true ...@@ -11,7 +11,7 @@ trim_trailing_whitespace = true
[*.go] [*.go]
indent_style = tab indent_style = tab
[*.proto] [{*.proto,*.html}]
indent_size = 2 indent_size = 2
[{Makefile,*.mk}] [{Makefile,*.mk}]
......
...@@ -632,13 +632,39 @@ func (s *Server) handleApproval(w http.ResponseWriter, r *http.Request) { ...@@ -632,13 +632,39 @@ func (s *Server) handleApproval(w http.ResponseWriter, r *http.Request) {
switch r.Method { switch r.Method {
case http.MethodGet: case http.MethodGet:
if r.FormValue("alreadyApproved") == "true" {
s.sendCodeResponse(w, r, authReq)
return
}
client, err := s.storage.GetClient(ctx, authReq.ClientID) client, err := s.storage.GetClient(ctx, authReq.ClientID)
if err != nil { if err != nil {
s.logger.ErrorContext(r.Context(), "Failed to get client", "client_id", authReq.ClientID, "err", err) s.logger.ErrorContext(r.Context(), "Failed to get client", "client_id", authReq.ClientID, "err", err)
s.renderError(r, w, http.StatusInternalServerError, "Failed to retrieve client.") s.renderError(r, w, http.StatusInternalServerError, "Failed to retrieve client.")
return return
} }
if err := s.templates.approval(r, w, authReq.ID, authReq.Claims.Username, client.Name, authReq.Scopes); err != nil {
approvalSkip := (*approvalSkipData)(nil)
if sub, err := genSubject(authReq.Claims.UserID, authReq.ConnectorID); err == nil {
h := hmac.New(sha256.New, []byte(client.Secret))
h.Write([]byte(client.ID))
h.Write([]byte(sub))
key := base64.RawURLEncoding.EncodeToString(h.Sum(nil))
h = hmac.New(sha256.New, []byte(client.Secret))
h.Write([]byte(client.ID))
scopes := make([]string, len(authReq.Scopes))
copy(scopes, authReq.Scopes)
sort.Strings(scopes)
for _, scope := range scopes {
h.Write([]byte(scope))
}
value := base64.RawURLEncoding.EncodeToString(h.Sum(nil))
approvalSkip = &approvalSkipData{key, value}
}
if err := s.templates.approval(r, w, authReq.ID, authReq.Claims.Username, client.Name, approvalSkip, authReq.Scopes); err != nil {
s.logger.ErrorContext(r.Context(), "server template error", "err", err) s.logger.ErrorContext(r.Context(), "server template error", "err", err)
} }
case http.MethodPost: case http.MethodPost:
......
...@@ -255,6 +255,11 @@ type connectorInfo struct { ...@@ -255,6 +255,11 @@ type connectorInfo struct {
Type string Type string
} }
type approvalSkipData struct {
Key string
Value string
}
type byName []connectorInfo type byName []connectorInfo
func (n byName) Len() int { return len(n) } func (n byName) Len() int { return len(n) }
...@@ -306,7 +311,7 @@ func (t *templates) password(r *http.Request, w http.ResponseWriter, postURL, la ...@@ -306,7 +311,7 @@ func (t *templates) password(r *http.Request, w http.ResponseWriter, postURL, la
return renderTemplate(w, t.passwordTmpl, data) return renderTemplate(w, t.passwordTmpl, data)
} }
func (t *templates) approval(r *http.Request, w http.ResponseWriter, authReqID, username, clientName string, scopes []string) error { func (t *templates) approval(r *http.Request, w http.ResponseWriter, authReqID, username, clientName string, approvalSkip *approvalSkipData, scopes []string) error {
accesses := []string{} accesses := []string{}
for _, scope := range scopes { for _, scope := range scopes {
access, ok := scopeDescriptions[scope] access, ok := scopeDescriptions[scope]
...@@ -316,12 +321,13 @@ func (t *templates) approval(r *http.Request, w http.ResponseWriter, authReqID, ...@@ -316,12 +321,13 @@ func (t *templates) approval(r *http.Request, w http.ResponseWriter, authReqID,
} }
sort.Strings(accesses) sort.Strings(accesses)
data := struct { data := struct {
User string User string
Client string ApprovalSkip *approvalSkipData
AuthReqID string Client string
Scopes []string AuthReqID string
ReqPath string Scopes []string
}{username, clientName, authReqID, accesses, r.URL.Path} ReqPath string
}{username, approvalSkip, clientName, authReqID, accesses, r.URL.Path}
return renderTemplate(w, t.approvalTmpl, data) return renderTemplate(w, t.approvalTmpl, data)
} }
......
{{ template "header.html" . }} {{ template "header.html" . }}
{{- with .ApprovalSkip }}
{{- /*
WARNING: The following script should be the very first thing within this html snippet.
*/ -}}
<script>
{
const approvalSkipKey = "approvalSkip.{{ .Key }}";
const approvalSkipValue = "{{ .Value }}";
const lastApprovalSkipValue = localStorage.getItem(approvalSkipKey);
if (lastApprovalSkipValue && lastApprovalSkipValue === approvalSkipValue) {
const location = new URL(window.location.href);
location.searchParams.set("alreadyApproved", "true");
window.location.replace(location.href);
}
function removeScopes() {
localStorage.removeItem(approvalSkipKey);
}
function saveScopes() {
if (document.getElementById("saveAccessGrant")?.checked) {
localStorage.setItem(approvalSkipKey, approvalSkipValue);
} else {
removeScopes()
}
}
window.addEventListener("load", () => {
document.getElementById("save-access-grant-container").style.display = null;
});
}
</script>
{{- end }}
<div class="theme-panel"> <div class="theme-panel">
<h2 class="theme-heading">Grant Access</h2> <h2 class="theme-heading">Grant Access</h2>
<hr class="dex-separator"> <hr class="dex-separator">
<div> <div>
{{ if .Scopes }} {{ if .Scopes }}
<div class="dex-subtle-text">{{ .Client }} would like to:</div> <div class="dex-subtle-text">{{ .Client }} would like to:</div>
<ul class="dex-list"> <ul class="dex-list">
{{ range $scope := .Scopes }} {{ range $scope := .Scopes }}
<li>{{ $scope }}</li> <li>{{ $scope }}</li>
{{ end }} {{ end }}
</ul> </ul>
{{ else }} {{ else }}
<div class="dex-subtle-text">{{ .Client }} has not requested any personal information</div> <div class="dex-subtle-text">{{ .Client }} has not requested any personal information</div>
{{ end }} {{ end }}
</div> </div>
<hr class="dex-separator"> <hr class="dex-separator">
<div> <div>
<div id="save-access-grant-container" style="display: none">
<input type="checkbox" id="save-access-grant">
<label for="save-access-grant">
Save access grant for future logins
</label>
</div>
<div class="theme-form-row"> <div class="theme-form-row">
<form method="post"> <form method="post" onsubmit="saveScopes()">
<input type="hidden" name="req" value="{{ .AuthReqID }}"/> <input type="hidden" name="req" value="{{ .AuthReqID }}"/>
<input type="hidden" name="approval" value="approve"> <input type="hidden" name="approval" value="approve">
<button type="submit" class="dex-btn theme-btn--success"> <button type="submit" class="dex-btn theme-btn--success">
<span class="dex-btn-text">Grant Access</span> <span class="dex-btn-text">Grant Access</span>
</button> </button>
</form> </form>
</div> </div>
<div class="theme-form-row"> <div class="theme-form-row">
<form method="post"> <form method="post" onsubmit="removeScopes()">
<input type="hidden" name="req" value="{{ .AuthReqID }}"/> <input type="hidden" name="req" value="{{ .AuthReqID }}"/>
<input type="hidden" name="approval" value="rejected"> <input type="hidden" name="approval" value="rejected">
<button type="submit" class="dex-btn theme-btn-provider"> <button type="submit" class="dex-btn theme-btn-provider">
<span class="dex-btn-text">Cancel</span> <span class="dex-btn-text">Cancel</span>
</button> </button>
</form> </form>
</div> </div>
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment