Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit a8e7f32

Browse files
authored
fix: redirect OAuth2 authorization page to dashboard (#24499)
Currently when a user clicks either the Cancel or Allow button on the authorization page the client app URI is executed but the page does not land to the main dashboard page, leaving the two buttons open for multiple clicks from the user. Aside from the potential problems it might cause by activating the callback URI multiple times, the page also provides poor UX because users usually expect the authorization tab to return to the dashboard. The consent page now executes the OAuth2 callback (auth code on Allow, `access_denied` on Cancel) and hides the two buttons and updates the existing description with a user instruction to close the window. Initial implementation relied on a pop-up window executing the callback while the main window was redirected to the dashboard main page. - resolves #20323 <!-- If you have used AI to produce some or all of this PR, please ensure you have read our [AI Contribution guidelines](https://coder.com/docs/about/contributing/AI_CONTRIBUTING) before submitting. -->
1 parent ad30951 commit a8e7f32

4 files changed

Lines changed: 61 additions & 22 deletions

File tree

coderd/oauth2provider/authorize.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -175,10 +175,10 @@ func ShowAuthorizePage(accessURL *url.URL) http.HandlerFunc {
175175
AppName: app.Name,
176176
// #nosec G203 -- The scheme is validated by
177177
// codersdk.ValidateRedirectURIScheme above.
178-
CancelURI: htmltemplate.URL(cancelURI),
179-
RedirectURI: r.URL.String(),
180-
CSRFToken: nosurf.Token(r),
181-
Username: ua.FriendlyName,
178+
CancelURI: htmltemplate.URL(cancelURI),
179+
DashboardURL: accessURL.String(),
180+
CSRFToken: nosurf.Token(r),
181+
Username: ua.FriendlyName,
182182
})
183183
}
184184
}

coderd/oauth2provider/authorize_test.go

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,17 @@ func TestOAuthConsentFormIncludesCSRFToken(t *testing.T) {
2020
rec := httptest.NewRecorder()
2121

2222
site.RenderOAuthAllowPage(rec, req, site.RenderOAuthAllowData{
23-
AppName: "Test OAuth App",
24-
CancelURI: htmltemplate.URL("https://coder.com/cancel"),
25-
RedirectURI: "https://coder.com/oauth2/authorize?client_id=test",
26-
CSRFToken: csrfFieldValue,
27-
Username: "test-user",
23+
AppName: "Test OAuth App",
24+
CancelURI: htmltemplate.URL("https://coder.com/cancel"),
25+
DashboardURL: "https://coder.com/",
26+
CSRFToken: csrfFieldValue,
27+
Username: "test-user",
2828
})
2929

3030
require.Equal(t, http.StatusOK, rec.Result().StatusCode)
31-
assert.Contains(t, rec.Body.String(), `name="csrf_token"`)
32-
assert.Contains(t, rec.Body.String(), `value="`+csrfFieldValue+`"`)
31+
body := rec.Body.String()
32+
assert.Contains(t, body, `name="csrf_token"`)
33+
assert.Contains(t, body, `value="`+csrfFieldValue+`"`)
34+
assert.Contains(t, body, `id="allow-form"`)
35+
assert.Contains(t, body, `id="cancel-link"`)
3336
}

site/site.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -799,12 +799,12 @@ func (jfs justFilesSystem) Open(name string) (fs.File, error) {
799799
// RenderOAuthAllowData contains the variables that are found in
800800
// site/static/oauth2allow.html.
801801
type RenderOAuthAllowData struct {
802-
AppIcon string
803-
AppName string
804-
CancelURI htmltemplate.URL
805-
RedirectURI string
806-
CSRFToken string
807-
Username string
802+
AppIcon string
803+
AppName string
804+
CancelURI htmltemplate.URL
805+
DashboardURL string
806+
CSRFToken string
807+
Username string
808808
}
809809

810810
// RenderOAuthAllowPage renders the static page for a user to "Allow" an create

site/static/oauth2allow.html

Lines changed: 41 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@
6464
line-height: 140%;
6565
}
6666

67-
.user-name {
67+
.user-name {
6868
font-weight: bold;
6969
}
7070

@@ -113,17 +113,53 @@
113113
<img class="coder-svg" src="/icon/coder.svg" alt="Coder" />
114114
</div>
115115
<h1>Authorize {{ .AppName }}</h1>
116-
<p>
116+
<p id="description">
117117
Allow {{ .AppName }} to have full access to your
118118
<span class="user-name">{{ .Username }}</span> account?
119119
</p>
120-
<div class="button-group">
121-
<form method="POST" style="display: inline;">
120+
<div class="button-group" id="button-group">
121+
<form id="allow-form" method="POST" style="display: inline;">
122122
<input type="hidden" name="csrf_token" value="{{ .CSRFToken }}" />
123123
<button type="submit" class="primary-button">Allow</button>
124124
</form>
125-
<a href="{{ .CancelURI }}">Cancel</a>
125+
<a id="cancel-link" href="{{ .CancelURI }}">Cancel</a>
126126
</div>
127127
</div>
128+
<script>
129+
(function () {
130+
var appName = "{{ .AppName }}";
131+
var description = document.getElementById("description");
132+
var buttonGroup = document.getElementById("button-group");
133+
var allowForm = document.getElementById("allow-form");
134+
var cancelLink = document.getElementById("cancel-link");
135+
136+
function showFeedback(message) {
137+
buttonGroup.style.display = "none";
138+
description.textContent = message;
139+
}
140+
141+
allowForm.addEventListener("submit", function (e) {
142+
e.preventDefault();
143+
showFeedback(
144+
appName +
145+
" is now authorized to access your account. This window can now be closed."
146+
);
147+
setTimeout(function () {
148+
allowForm.submit();
149+
}, 500);
150+
});
151+
152+
cancelLink.addEventListener("click", function (e) {
153+
e.preventDefault();
154+
showFeedback(
155+
appName +
156+
" was denied access to your account. This window can now be closed."
157+
);
158+
setTimeout(function () {
159+
window.location.href = cancelLink.href;
160+
}, 500);
161+
});
162+
})();
163+
</script>
128164
</body>
129165
</html>

0 commit comments

Comments
 (0)