fix: web login (#14378)

#### PR Dependency Tree


* **PR #14378** 👈

This tree was auto-generated by
[Charcoal](https://github.com/danerwilliams/charcoal)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Added a deprecated GET sign-out endpoint for backward compatibility
with legacy clients.

* **Improvements**
* Updated magic-link and OAuth flows to always generate and manage
client nonces; native clients use a nonce, web preserves cross-device
behavior.

* **Tests**
* Added tests covering the deprecated sign-out flow and OAuth preflight
client_nonce handling.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
DarkSky
2026-02-06 03:05:50 +08:00
committed by GitHub
parent 5a51d447fb
commit 8c15df489b
4 changed files with 114 additions and 9 deletions
@@ -63,7 +63,11 @@ export class AuthService extends Service {
redirectUrl?: string // url to redirect to after signed-in
) {
track.$.$.auth.signIn({ method: 'magic-link' });
this.setClientNonce();
// Only native clients use `client_nonce` for magic-link/otp sign-in.
// Web needs to keep cross-device magic-link compatibility.
const magicLinkClientNonce = BUILD_CONFIG.isNative
? this.setClientNonce()
: undefined;
try {
const scheme = this.urlService.getClientScheme();
const magicLinkUrlParams = new URLSearchParams();
@@ -80,7 +84,7 @@ export class AuthService extends Service {
// we call it [callbackUrl] instead of [redirect_uri]
// to make it clear the url is used to finish the sign-in process instead of redirect after signed-in
callbackUrl: `/magic-link?${magicLinkUrlParams.toString()}`,
client_nonce: this.store.getClientNonce(),
client_nonce: magicLinkClientNonce,
}),
headers: {
'content-type': 'application/json',
@@ -117,7 +121,8 @@ export class AuthService extends Service {
client: string,
/** @deprecated*/ redirectUrl?: string
): Promise<Record<string, string>> {
this.setClientNonce();
// OAuth callback requires `client_nonce` for all clients (including web).
const clientNonce = this.setClientNonce();
try {
const res = await this.fetchService.fetch('/api/oauth/preflight', {
method: 'POST',
@@ -125,7 +130,7 @@ export class AuthService extends Service {
provider,
client,
redirect_uri: redirectUrl,
client_nonce: this.store.getClientNonce(),
client_nonce: clientNonce,
}),
headers: {
'content-type': 'application/json',
@@ -241,10 +246,9 @@ export class AuthService extends Service {
return headers;
}
private setClientNonce() {
if (BUILD_CONFIG.isNative) {
// send random client nonce on native app
this.store.setClientNonce(nanoid());
}
private setClientNonce(): string {
const nonce = nanoid();
this.store.setClientNonce(nonce);
return nonce;
}
}