Skip to content

Commit 39ee202

Browse files
committed
feat(device): no-session UX on /device/authorize + i18n refresh
No active session now shows a "Sign in to continue" CTA (+ "You'll be asked to sign in before authorizing." note) instead of an Authorize button that silently bounced to /login; Deny stays available sessionlessly. Update inline-action-error test (signed-in consent fixture + new no-session assertion) and refresh en.po.
1 parent 769516a commit 39ee202

4 files changed

Lines changed: 81 additions & 33 deletions

File tree

app/modules/i18n/locales/en.po

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ msgstr "Authorization complete"
100100
msgid "Authorization failed"
101101
msgstr "Authorization failed"
102102

103-
#: app/routes/device/authorize.tsx:157
103+
#: app/routes/device/authorize.tsx:162
104104
msgid "Authorize"
105105
msgstr "Authorize"
106106

@@ -212,7 +212,8 @@ msgstr "Create a new account"
212212
msgid "Create account"
213213
msgstr "Create account"
214214

215-
#: app/routes/device/authorize.tsx:167
215+
#: app/routes/device/authorize.tsx:172
216+
#: app/routes/device/authorize.tsx:198
216217
msgid "Deny"
217218
msgstr "Deny"
218219

@@ -541,6 +542,10 @@ msgstr "Sign in"
541542
msgid "Sign in again"
542543
msgstr "Sign in again"
543544

545+
#: app/routes/device/authorize.tsx:184
546+
msgid "Sign in to continue"
547+
msgstr "Sign in to continue"
548+
544549
#: app/routes/sso/link.tsx:48
545550
msgid "Sign in with"
546551
msgstr "Sign in with"
@@ -790,6 +795,10 @@ msgstr "You may return to your device."
790795
msgid "You must be signed in to link an external account."
791796
msgstr "You must be signed in to link an external account."
792797

798+
#: app/routes/device/authorize.tsx:134
799+
msgid "You'll be asked to sign in before authorizing."
800+
msgstr "You'll be asked to sign in before authorizing."
801+
793802
#: app/routes/signup/password.tsx:159
794803
msgid "You'll need to set a password to complete your signup."
795804
msgstr "You'll need to set a password to complete your signup."

app/routes/__tests__/inline-action-error.test.tsx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,9 @@ describe('device/authorize — inline ACTION error in the consent form (no toast
146146
scope: ['read'],
147147
deviceAuthId: 'dev-1',
148148
requestId: 'rq-1',
149+
// Signed-in consent: the authorize/deny action-error path only occurs with an active
150+
// session (a sessionless authorize redirects to /login rather than producing an error).
151+
activeLoginName: 'user@example.test',
149152
};
150153

151154
it('renders the deny/authorize action error inline in a role="alert" banner inside the consent form', async () => {
@@ -158,6 +161,14 @@ describe('device/authorize — inline ACTION error in the consent form (no toast
158161
expect(screen.getByRole('button', { name: /Authorize/ })).toBeInTheDocument();
159162
});
160163

164+
it('with no active session, shows a "Sign in to continue" CTA instead of Authorize', async () => {
165+
mountAt(DeviceAuthorize, '/device/authorize', { ...consentLoaderData, activeLoginName: null });
166+
expect(await screen.findByRole('link', { name: /Sign in to continue/ })).toBeInTheDocument();
167+
expect(screen.queryByRole('button', { name: /Authorize/ })).not.toBeInTheDocument();
168+
// Deny stays available — a sessionless user can still reject an unrecognized device.
169+
expect(screen.getByRole('button', { name: /Deny/ })).toBeInTheDocument();
170+
});
171+
161172
it('renders NO alert banner when there is no action error', async () => {
162173
mountAt(DeviceAuthorize, '/device/authorize', consentLoaderData);
163174
await screen.findByText(/Authorize device/);

app/routes/accounts.tsx

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -97,11 +97,7 @@ export default function AccountPicker() {
9797
{/* Carry the ceremony requestId so a fresh "add account" login resumes the
9898
OIDC/SAML/device callback (like the switch form) rather than dead-ending at
9999
the default post-login redirect. */}
100-
<LinkButton
101-
theme="link"
102-
type="quaternary"
103-
as={Link}
104-
href={addAccountHref}>
100+
<LinkButton theme="link" type="quaternary" as={Link} href={addAccountHref}>
105101
<Trans>Add an account</Trans>
106102
</LinkButton>
107103
</div>

app/routes/device/authorize.tsx

Lines changed: 58 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,11 @@ export default function DeviceAuthorize() {
129129
<Trans>Use a different account</Trans>
130130
</LinkButton>
131131
</span>
132-
) : null}
132+
) : (
133+
<span className="mt-2 block">
134+
<Trans>You'll be asked to sign in before authorizing.</Trans>
135+
</span>
136+
)}
133137
</>
134138
}>
135139
<div className="flex flex-col gap-4">
@@ -142,32 +146,60 @@ export default function DeviceAuthorize() {
142146
))}
143147
</div>
144148
)}
145-
<RRForm method="post" className="flex w-full flex-col gap-4">
146-
<AuthFormFields csrf={csrfToken} />
147-
<input type="hidden" name="deviceAuthId" value={deviceAuthId} />
148-
<input type="hidden" name="requestId" value={requestId} />
149-
<FormError>{errorMessage}</FormError>
150-
<div className="flex flex-col gap-3">
151-
<Button
152-
htmlType="submit"
153-
name="decision"
154-
className="w-full"
155-
value="authorize"
156-
disabled={isSubmitting}>
157-
<Trans>Authorize</Trans>
158-
</Button>
159-
<Button
160-
htmlType="submit"
161-
name="decision"
162-
value="deny"
163-
type="secondary"
164-
theme="outline"
165-
className="w-full"
166-
disabled={isSubmitting}>
167-
<Trans>Deny</Trans>
168-
</Button>
149+
{activeLoginName ? (
150+
<RRForm method="post" className="flex w-full flex-col gap-4">
151+
<AuthFormFields csrf={csrfToken} />
152+
<input type="hidden" name="deviceAuthId" value={deviceAuthId} />
153+
<input type="hidden" name="requestId" value={requestId} />
154+
<FormError>{errorMessage}</FormError>
155+
<div className="flex flex-col gap-3">
156+
<Button
157+
htmlType="submit"
158+
name="decision"
159+
className="w-full"
160+
value="authorize"
161+
disabled={isSubmitting}>
162+
<Trans>Authorize</Trans>
163+
</Button>
164+
<Button
165+
htmlType="submit"
166+
name="decision"
167+
value="deny"
168+
type="secondary"
169+
theme="outline"
170+
className="w-full"
171+
disabled={isSubmitting}>
172+
<Trans>Deny</Trans>
173+
</Button>
174+
</div>
175+
</RRForm>
176+
) : (
177+
// Not signed in: authorizing requires a session. Lead with a clear "Sign in to
178+
// continue" CTA (after login the device auto-completes — no second consent click)
179+
// instead of an Authorize button that silently bounces to /login. Deny still works
180+
// sessionlessly, so a user can reject an unrecognized device without signing in.
181+
<div className="flex w-full flex-col gap-3">
182+
<FormError>{errorMessage}</FormError>
183+
<LinkButton type="primary" block as={Link} href={paths.login.index({ requestId })}>
184+
<Trans>Sign in to continue</Trans>
185+
</LinkButton>
186+
<RRForm method="post">
187+
<AuthFormFields csrf={csrfToken} />
188+
<input type="hidden" name="deviceAuthId" value={deviceAuthId} />
189+
<input type="hidden" name="requestId" value={requestId} />
190+
<Button
191+
htmlType="submit"
192+
name="decision"
193+
value="deny"
194+
type="secondary"
195+
theme="outline"
196+
block
197+
disabled={isSubmitting}>
198+
<Trans>Deny</Trans>
199+
</Button>
200+
</RRForm>
169201
</div>
170-
</RRForm>
202+
)}
171203
</div>
172204
</AuthCard>
173205
);

0 commit comments

Comments
 (0)