mirror of
https://github.com/immich-app/immich.git
synced 2026-04-18 12:19:35 +00:00
chore: rename linkToken to oauthLinkToken
This commit is contained in:
@@ -367,7 +367,7 @@ describe(`/oauth`, () => {
|
||||
expect(status).toBe(403);
|
||||
expect(body.message).toBe('oauth_account_link_required');
|
||||
expect(body.userEmail).toBe('oauth-user3@immich.app');
|
||||
expect(body.linkToken).toBeDefined();
|
||||
expect(body.oauthLinkToken).toBeDefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
20
mobile/openapi/lib/model/login_credential_dto.dart
generated
20
mobile/openapi/lib/model/login_credential_dto.dart
generated
@@ -14,32 +14,49 @@ class LoginCredentialDto {
|
||||
/// Returns a new [LoginCredentialDto] instance.
|
||||
LoginCredentialDto({
|
||||
required this.email,
|
||||
this.oauthLinkToken,
|
||||
required this.password,
|
||||
});
|
||||
|
||||
/// User email
|
||||
String email;
|
||||
|
||||
/// OAuth link token to consume on successful login
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
String? oauthLinkToken;
|
||||
|
||||
/// User password
|
||||
String password;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) => identical(this, other) || other is LoginCredentialDto &&
|
||||
other.email == email &&
|
||||
other.oauthLinkToken == oauthLinkToken &&
|
||||
other.password == password;
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
// ignore: unnecessary_parenthesis
|
||||
(email.hashCode) +
|
||||
(oauthLinkToken == null ? 0 : oauthLinkToken!.hashCode) +
|
||||
(password.hashCode);
|
||||
|
||||
@override
|
||||
String toString() => 'LoginCredentialDto[email=$email, password=$password]';
|
||||
String toString() => 'LoginCredentialDto[email=$email, oauthLinkToken=$oauthLinkToken, password=$password]';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
json[r'email'] = this.email;
|
||||
if (this.oauthLinkToken != null) {
|
||||
json[r'oauthLinkToken'] = this.oauthLinkToken;
|
||||
} else {
|
||||
// json[r'oauthLinkToken'] = null;
|
||||
}
|
||||
json[r'password'] = this.password;
|
||||
return json;
|
||||
}
|
||||
@@ -54,6 +71,7 @@ class LoginCredentialDto {
|
||||
|
||||
return LoginCredentialDto(
|
||||
email: mapValueOfType<String>(json, r'email')!,
|
||||
oauthLinkToken: mapValueOfType<String>(json, r'oauthLinkToken'),
|
||||
password: mapValueOfType<String>(json, r'password')!,
|
||||
);
|
||||
}
|
||||
|
||||
20
mobile/openapi/lib/model/sign_up_dto.dart
generated
20
mobile/openapi/lib/model/sign_up_dto.dart
generated
@@ -15,6 +15,7 @@ class SignUpDto {
|
||||
SignUpDto({
|
||||
required this.email,
|
||||
required this.name,
|
||||
this.oauthLinkToken,
|
||||
required this.password,
|
||||
});
|
||||
|
||||
@@ -24,6 +25,15 @@ class SignUpDto {
|
||||
/// User name
|
||||
String name;
|
||||
|
||||
/// OAuth link token to consume on successful login
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
String? oauthLinkToken;
|
||||
|
||||
/// User password
|
||||
String password;
|
||||
|
||||
@@ -31,6 +41,7 @@ class SignUpDto {
|
||||
bool operator ==(Object other) => identical(this, other) || other is SignUpDto &&
|
||||
other.email == email &&
|
||||
other.name == name &&
|
||||
other.oauthLinkToken == oauthLinkToken &&
|
||||
other.password == password;
|
||||
|
||||
@override
|
||||
@@ -38,15 +49,21 @@ class SignUpDto {
|
||||
// ignore: unnecessary_parenthesis
|
||||
(email.hashCode) +
|
||||
(name.hashCode) +
|
||||
(oauthLinkToken == null ? 0 : oauthLinkToken!.hashCode) +
|
||||
(password.hashCode);
|
||||
|
||||
@override
|
||||
String toString() => 'SignUpDto[email=$email, name=$name, password=$password]';
|
||||
String toString() => 'SignUpDto[email=$email, name=$name, oauthLinkToken=$oauthLinkToken, password=$password]';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
json[r'email'] = this.email;
|
||||
json[r'name'] = this.name;
|
||||
if (this.oauthLinkToken != null) {
|
||||
json[r'oauthLinkToken'] = this.oauthLinkToken;
|
||||
} else {
|
||||
// json[r'oauthLinkToken'] = null;
|
||||
}
|
||||
json[r'password'] = this.password;
|
||||
return json;
|
||||
}
|
||||
@@ -62,6 +79,7 @@ class SignUpDto {
|
||||
return SignUpDto(
|
||||
email: mapValueOfType<String>(json, r'email')!,
|
||||
name: mapValueOfType<String>(json, r'name')!,
|
||||
oauthLinkToken: mapValueOfType<String>(json, r'oauthLinkToken'),
|
||||
password: mapValueOfType<String>(json, r'password')!,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -18013,7 +18013,7 @@
|
||||
"pattern": "^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$",
|
||||
"type": "string"
|
||||
},
|
||||
"linkToken": {
|
||||
"oauthLinkToken": {
|
||||
"description": "OAuth link token to consume on successful login",
|
||||
"type": "string"
|
||||
},
|
||||
@@ -21799,15 +21799,15 @@
|
||||
"pattern": "^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$",
|
||||
"type": "string"
|
||||
},
|
||||
"linkToken": {
|
||||
"description": "OAuth link token to consume on successful login",
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"description": "User name",
|
||||
"example": "Admin",
|
||||
"type": "string"
|
||||
},
|
||||
"oauthLinkToken": {
|
||||
"description": "OAuth link token to consume on successful login",
|
||||
"type": "string"
|
||||
},
|
||||
"password": {
|
||||
"description": "User password",
|
||||
"example": "password",
|
||||
|
||||
@@ -1008,10 +1008,10 @@ export type AssetOcrResponseDto = {
|
||||
export type SignUpDto = {
|
||||
/** User email */
|
||||
email: string;
|
||||
/** OAuth link token to consume on successful login */
|
||||
linkToken?: string;
|
||||
/** User name */
|
||||
name: string;
|
||||
/** OAuth link token to consume on successful login */
|
||||
oauthLinkToken?: string;
|
||||
/** User password */
|
||||
password: string;
|
||||
};
|
||||
@@ -1027,7 +1027,7 @@ export type LoginCredentialDto = {
|
||||
/** User email */
|
||||
email: string;
|
||||
/** OAuth link token to consume on successful login */
|
||||
linkToken?: string;
|
||||
oauthLinkToken?: string;
|
||||
/** User password */
|
||||
password: string;
|
||||
};
|
||||
|
||||
@@ -23,7 +23,7 @@ const LoginCredentialSchema = z
|
||||
.object({
|
||||
email: toEmail.describe('User email').meta({ example: 'testuser@email.com' }),
|
||||
password: z.string().describe('User password').meta({ example: 'password' }),
|
||||
linkToken: z.string().optional().describe('OAuth link token to consume on successful login'),
|
||||
oauthLinkToken: z.string().optional().describe('OAuth link token to consume on successful login'),
|
||||
})
|
||||
.meta({ id: 'LoginCredentialDto' });
|
||||
|
||||
|
||||
@@ -89,7 +89,7 @@ describe(AuthService.name, () => {
|
||||
expect(mocks.user.getByEmail).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should link an OAuth account when linkToken is provided', async () => {
|
||||
it('should link an OAuth account when oauthLinkToken is provided', async () => {
|
||||
const user = UserFactory.create({ password: 'immich_password' });
|
||||
const session = SessionFactory.create();
|
||||
mocks.user.getByEmail.mockResolvedValue(user);
|
||||
@@ -104,20 +104,20 @@ describe(AuthService.name, () => {
|
||||
});
|
||||
mocks.user.update.mockResolvedValue(user);
|
||||
|
||||
await sut.login({ email, password: 'password', linkToken: 'plain-token' }, loginDetails);
|
||||
await sut.login({ email, password: 'password', oauthLinkToken: 'plain-token' }, loginDetails);
|
||||
|
||||
expect(mocks.oauthLinkToken.consumeToken).toHaveBeenCalledTimes(1);
|
||||
expect(mocks.user.update).toHaveBeenCalledWith(user.id, { oauthId: 'oauth-sub-123' });
|
||||
});
|
||||
|
||||
it('should reject login with invalid linkToken', async () => {
|
||||
it('should reject login with invalid oauthLinkToken', async () => {
|
||||
const user = UserFactory.create({ password: 'immich_password' });
|
||||
mocks.user.getByEmail.mockResolvedValue(user);
|
||||
mocks.oauthLinkToken.consumeToken.mockResolvedValue(null as any);
|
||||
|
||||
await expect(sut.login({ email, password: 'password', linkToken: 'bad-token' }, loginDetails)).rejects.toThrow(
|
||||
'Invalid or expired link token',
|
||||
);
|
||||
await expect(
|
||||
sut.login({ email, password: 'password', oauthLinkToken: 'bad-token' }, loginDetails),
|
||||
).rejects.toThrow('Invalid or expired link token');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -75,8 +75,8 @@ export class AuthService extends BaseService {
|
||||
throw new UnauthorizedException('Incorrect email or password');
|
||||
}
|
||||
|
||||
if (dto.linkToken) {
|
||||
const hashedToken = this.cryptoRepository.hashSha256(dto.linkToken);
|
||||
if (dto.oauthLinkToken) {
|
||||
const hashedToken = this.cryptoRepository.hashSha256(dto.oauthLinkToken);
|
||||
const record = await this.oauthLinkTokenRepository.consumeToken(hashedToken);
|
||||
if (!record) {
|
||||
throw new BadRequestException('Invalid or expired link token');
|
||||
@@ -350,7 +350,7 @@ export class AuthService extends BaseService {
|
||||
throw new ForbiddenException({
|
||||
message: 'oauth_account_link_required',
|
||||
userEmail: emailUser.email,
|
||||
linkToken: plainToken,
|
||||
oauthLinkToken: plainToken,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ export const Docs = {
|
||||
export const Route = {
|
||||
// auth
|
||||
login: (params?: { continue?: string; autoLaunch?: 0 | 1 }) => '/auth/login' + asQueryString(params),
|
||||
authLink: (params?: { linkToken?: string; email?: string }) => '/auth/link' + asQueryString(params),
|
||||
authLink: (params?: { oauthLinkToken?: string; email?: string }) => '/auth/link' + asQueryString(params),
|
||||
logout: (params?: { continue?: string }) => '/auth/logout' + asQueryString(params),
|
||||
register: () => '/auth/register',
|
||||
changePassword: () => '/auth/change-password',
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
let { data }: Props = $props();
|
||||
|
||||
let linkToken = $state(data.linkToken);
|
||||
let oauthLinkToken = $state(data.oauthLinkToken);
|
||||
let email = $state(data.email || authManager.user?.email || '');
|
||||
let password = $state('');
|
||||
let errorMessage = $state('');
|
||||
@@ -33,7 +33,7 @@
|
||||
try {
|
||||
errorMessage = '';
|
||||
loading = true;
|
||||
const user = await login({ loginCredentialDto: { email, password, linkToken } });
|
||||
const user = await login({ loginCredentialDto: { email, password, oauthLinkToken } });
|
||||
eventManager.emit('AuthLogin', user);
|
||||
await authManager.refresh();
|
||||
toastManager.primary($t('linked_oauth_account'));
|
||||
|
||||
@@ -2,7 +2,7 @@ import { getFormatter } from '$lib/utils/i18n';
|
||||
import type { PageLoad } from './$types';
|
||||
|
||||
export const load = (async ({ url }) => {
|
||||
const linkToken = url.searchParams.get('linkToken') || '';
|
||||
const oauthLinkToken = url.searchParams.get('oauthLinkToken') || '';
|
||||
const email = url.searchParams.get('email') || '';
|
||||
|
||||
const $t = await getFormatter();
|
||||
@@ -10,7 +10,7 @@ export const load = (async ({ url }) => {
|
||||
meta: {
|
||||
title: $t('link_to_oauth'),
|
||||
},
|
||||
linkToken,
|
||||
oauthLinkToken,
|
||||
email,
|
||||
};
|
||||
}) satisfies PageLoad;
|
||||
|
||||
@@ -72,7 +72,7 @@
|
||||
} catch (error) {
|
||||
if (isHttpError(error) && error.data?.message === 'oauth_account_link_required') {
|
||||
const errorData = error.data as unknown as Record<string, string>;
|
||||
await goto(Route.authLink({ linkToken: errorData.linkToken, email: errorData.userEmail }));
|
||||
await goto(Route.authLink({ oauthLinkToken: errorData.oauthLinkToken, email: errorData.userEmail }));
|
||||
return;
|
||||
}
|
||||
console.error('Error [login-form] [oauth.callback]', error);
|
||||
|
||||
Reference in New Issue
Block a user