V14: make v13 login screen work initially with Management API (#15170)
* make the login check a bit more robust to be able to handle both old postlogin and new management api response types * v14 only: update the login url to work with the management api (this will still work with the old backoffice to some extent) * Revert "v14 only: update the login url to work with the management api (this will still work with the old backoffice to some extent)" This reverts commit 0639ca80f0ce620b3555b959d5ff10678730acfd. * V14 only: additionally authenticate with the Management API to set the right cookies
This commit is contained in:
@@ -60,8 +60,6 @@ const createForm = (elements: HTMLElement[]) => {
|
||||
|
||||
@customElement('umb-auth')
|
||||
export default class UmbAuthElement extends LitElement {
|
||||
#returnPath = '';
|
||||
|
||||
/**
|
||||
* Disables the local login form and only allows external login providers.
|
||||
*
|
||||
@@ -89,12 +87,7 @@ export default class UmbAuthElement extends LitElement {
|
||||
|
||||
@property({ type: String, attribute: 'return-url' })
|
||||
set returnPath(value: string) {
|
||||
this.#returnPath = value;
|
||||
umbAuthContext.returnPath = this.returnPath;
|
||||
}
|
||||
get returnPath() {
|
||||
// Check if there is a ?redir querystring or else return the returnUrl attribute
|
||||
return new URLSearchParams(window.location.search).get('returnPath') || this.#returnPath;
|
||||
umbAuthContext.returnPath = value;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -15,7 +15,30 @@ export class UmbAuthContext implements IUmbAuthContext {
|
||||
|
||||
#authRepository = new UmbAuthRepository();
|
||||
|
||||
public returnPath = '';
|
||||
#returnPath = '';
|
||||
|
||||
set returnPath(value: string) {
|
||||
this.#returnPath = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the return path from the query string.
|
||||
*
|
||||
* It will first look for a `ReturnUrl` parameter, then a `returnPath` parameter, and finally the `returnPath` property.
|
||||
*
|
||||
* @returns The return path from the query string.
|
||||
*/
|
||||
get returnPath(): string {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
let returnUrl = params.get('ReturnUrl') ?? params.get('returnPath') ?? this.#returnPath;
|
||||
|
||||
// Paths from the old Backoffice are encoded twice and need to be decoded,
|
||||
// but we don't want to decode the new paths coming from the Management API.
|
||||
if (returnUrl.indexOf('/security/back-office/authorize') === -1) {
|
||||
returnUrl = decodeURIComponent(returnUrl);
|
||||
}
|
||||
return returnUrl || '';
|
||||
}
|
||||
|
||||
async login(data: LoginRequestModel): Promise<LoginResponse> {
|
||||
return this.#authRepository.login(data);
|
||||
|
||||
@@ -10,254 +10,289 @@ import { umbLocalizationContext } from '../external/localization/localization-co
|
||||
export class UmbAuthRepository {
|
||||
readonly #authURL = 'backoffice/umbracoapi/authentication/postlogin';
|
||||
|
||||
public async login(data: LoginRequestModel): Promise<LoginResponse> {
|
||||
try {
|
||||
const request = new Request(this.#authURL, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
username: data.username,
|
||||
password: data.password,
|
||||
rememberMe: data.persist,
|
||||
}),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
const response = await fetch(request);
|
||||
public async login(data: LoginRequestModel): Promise<LoginResponse> {
|
||||
try {
|
||||
const request = new Request(this.#authURL, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
username: data.username,
|
||||
password: data.password,
|
||||
rememberMe: data.persist,
|
||||
}),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
const response = await fetch(request);
|
||||
|
||||
const text = await response.text();
|
||||
const responseData = JSON.parse(this.#removeAngularJSResponseData(text));
|
||||
const responseData: LoginResponse = {
|
||||
status: response.status
|
||||
};
|
||||
|
||||
return {
|
||||
status: response.status,
|
||||
error: response.ok ? undefined : await this.#getErrorText(response),
|
||||
data: responseData,
|
||||
twoFactorView: responseData?.twoFactorView,
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
status: 500,
|
||||
error: error instanceof Error ? error.message : 'Unknown error',
|
||||
};
|
||||
}
|
||||
}
|
||||
if (!response.ok) {
|
||||
responseData.error = await this.#getErrorText(response);
|
||||
return responseData;
|
||||
}
|
||||
|
||||
public async resetPassword(email: string): Promise<ResetPasswordResponse> {
|
||||
const request = new Request('backoffice/umbracoapi/authentication/PostRequestPasswordReset', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
email,
|
||||
}),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
const response = await fetch(request);
|
||||
// Additionally authenticate with the Management API
|
||||
await this.#managementApiLogin(data.username, data.password);
|
||||
|
||||
return {
|
||||
status: response.status,
|
||||
error: response.ok ? undefined : await this.#getErrorText(response),
|
||||
};
|
||||
}
|
||||
try {
|
||||
const text = await response.text();
|
||||
if (text) {
|
||||
responseData.data = JSON.parse(this.#removeAngularJSResponseData(text));
|
||||
}
|
||||
} catch {}
|
||||
|
||||
public async validatePasswordResetCode(user: string, code: string): Promise<ValidatePasswordResetCodeResponse> {
|
||||
const request = new Request('backoffice/umbracoapi/authentication/validatepasswordresetcode', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
userId: user,
|
||||
resetCode: code,
|
||||
}),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
const response = await fetch(request);
|
||||
return {
|
||||
status: response.status,
|
||||
data: responseData?.data,
|
||||
twoFactorView: responseData?.twoFactorView,
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
status: 500,
|
||||
error: error instanceof Error ? error.message : 'Unknown error',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
status: response.status,
|
||||
error: response.ok ? undefined : await this.#getErrorText(response),
|
||||
};
|
||||
}
|
||||
public async resetPassword(email: string): Promise<ResetPasswordResponse> {
|
||||
const request = new Request('backoffice/umbracoapi/authentication/PostRequestPasswordReset', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
email,
|
||||
}),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
const response = await fetch(request);
|
||||
|
||||
public async newPassword(password: string, resetCode: string, userId: number): Promise<LoginResponse> {
|
||||
const request = new Request('backoffice/umbracoapi/authentication/PostSetPassword', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
password,
|
||||
resetCode,
|
||||
userId,
|
||||
}),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
const response = await fetch(request);
|
||||
return {
|
||||
status: response.status,
|
||||
error: response.ok ? undefined : await this.#getErrorText(response),
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
status: response.status,
|
||||
error: response.ok ? undefined : await this.#getErrorText(response),
|
||||
};
|
||||
}
|
||||
public async validatePasswordResetCode(user: string, code: string): Promise<ValidatePasswordResetCodeResponse> {
|
||||
const request = new Request('backoffice/umbracoapi/authentication/validatepasswordresetcode', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
userId: user,
|
||||
resetCode: code,
|
||||
}),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
const response = await fetch(request);
|
||||
|
||||
public async newInvitedUserPassword(newPassWord: string): Promise<LoginResponse> {
|
||||
const request = new Request('backoffice/umbracoapi/authentication/PostSetInvitedUserPassword', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
newPassWord,
|
||||
}),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
const response = await fetch(request);
|
||||
return {
|
||||
status: response.status,
|
||||
error: response.ok ? undefined : await this.#getErrorText(response),
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
status: response.status,
|
||||
error: response.ok ? undefined : await this.#getErrorText(response),
|
||||
};
|
||||
}
|
||||
public async newPassword(password: string, resetCode: string, userId: number): Promise<LoginResponse> {
|
||||
const request = new Request('backoffice/umbracoapi/authentication/PostSetPassword', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
password,
|
||||
resetCode,
|
||||
userId,
|
||||
}),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
const response = await fetch(request);
|
||||
|
||||
public async getPasswordConfig(userId: string): Promise<any> {
|
||||
//TODO: Add type
|
||||
const request = new Request(`backoffice/umbracoapi/authentication/GetPasswordConfig?userId=${userId}`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
const response = await fetch(request);
|
||||
return {
|
||||
status: response.status,
|
||||
error: response.ok ? undefined : await this.#getErrorText(response),
|
||||
};
|
||||
}
|
||||
|
||||
// Check if response contains AngularJS response data
|
||||
if (response.ok) {
|
||||
let text = await response.text();
|
||||
text = this.#removeAngularJSResponseData(text);
|
||||
const data = JSON.parse(text);
|
||||
public async newInvitedUserPassword(newPassWord: string): Promise<LoginResponse> {
|
||||
const request = new Request('backoffice/umbracoapi/authentication/PostSetInvitedUserPassword', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
newPassWord,
|
||||
}),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
const response = await fetch(request);
|
||||
|
||||
return {
|
||||
status: response.status,
|
||||
data,
|
||||
};
|
||||
}
|
||||
return {
|
||||
status: response.status,
|
||||
error: response.ok ? undefined : await this.#getErrorText(response),
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
status: response.status,
|
||||
error: response.ok ? undefined : this.#getErrorText(response),
|
||||
};
|
||||
}
|
||||
public async getPasswordConfig(userId: string): Promise<any> {
|
||||
//TODO: Add type
|
||||
const request = new Request(`backoffice/umbracoapi/authentication/GetPasswordConfig?userId=${userId}`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
const response = await fetch(request);
|
||||
|
||||
public async getInvitedUser(): Promise<any> {
|
||||
//TODO: Add type
|
||||
const request = new Request('backoffice/umbracoapi/authentication/GetCurrentInvitedUser', {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
const response = await fetch(request);
|
||||
// Check if response contains AngularJS response data
|
||||
if (response.ok) {
|
||||
let text = await response.text();
|
||||
text = this.#removeAngularJSResponseData(text);
|
||||
const data = JSON.parse(text);
|
||||
|
||||
// Check if response contains AngularJS response data
|
||||
if (response.ok) {
|
||||
let text = await response.text();
|
||||
text = this.#removeAngularJSResponseData(text);
|
||||
const user = JSON.parse(text);
|
||||
return {
|
||||
status: response.status,
|
||||
data,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
status: response.status,
|
||||
user,
|
||||
};
|
||||
}
|
||||
return {
|
||||
status: response.status,
|
||||
error: response.ok ? undefined : this.#getErrorText(response),
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
status: response.status,
|
||||
error: this.#getErrorText(response),
|
||||
};
|
||||
}
|
||||
public async getInvitedUser(): Promise<any> {
|
||||
//TODO: Add type
|
||||
const request = new Request('backoffice/umbracoapi/authentication/GetCurrentInvitedUser', {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
const response = await fetch(request);
|
||||
|
||||
public async getMfaProviders(): Promise<MfaProvidersResponse> {
|
||||
const request = new Request('backoffice/umbracoapi/authentication/Get2faProviders', {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
const response = await fetch(request);
|
||||
// Check if response contains AngularJS response data
|
||||
if (response.ok) {
|
||||
let text = await response.text();
|
||||
text = this.#removeAngularJSResponseData(text);
|
||||
const user = JSON.parse(text);
|
||||
|
||||
// Check if response contains AngularJS response data
|
||||
if (response.ok) {
|
||||
let text = await response.text();
|
||||
text = this.#removeAngularJSResponseData(text);
|
||||
const providers = JSON.parse(text);
|
||||
return {
|
||||
status: response.status,
|
||||
user,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
status: response.status,
|
||||
providers,
|
||||
};
|
||||
}
|
||||
return {
|
||||
status: response.status,
|
||||
error: this.#getErrorText(response),
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
status: response.status,
|
||||
error: await this.#getErrorText(response),
|
||||
providers: [],
|
||||
};
|
||||
}
|
||||
public async getMfaProviders(): Promise<MfaProvidersResponse> {
|
||||
const request = new Request('backoffice/umbracoapi/authentication/Get2faProviders', {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
const response = await fetch(request);
|
||||
|
||||
public async validateMfaCode(code: string, provider: string): Promise<LoginResponse> {
|
||||
const request = new Request('backoffice/umbracoapi/authentication/PostVerify2faCode', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
code,
|
||||
provider,
|
||||
}),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
// Check if response contains AngularJS response data
|
||||
if (response.ok) {
|
||||
let text = await response.text();
|
||||
text = this.#removeAngularJSResponseData(text);
|
||||
const providers = JSON.parse(text);
|
||||
|
||||
const response = await fetch(request);
|
||||
return {
|
||||
status: response.status,
|
||||
providers,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
status: response.status,
|
||||
error: await this.#getErrorText(response),
|
||||
providers: [],
|
||||
};
|
||||
}
|
||||
|
||||
public async validateMfaCode(code: string, provider: string): Promise<LoginResponse> {
|
||||
const request = new Request('backoffice/umbracoapi/authentication/PostVerify2faCode', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
code,
|
||||
provider,
|
||||
}),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
const response = await fetch(request);
|
||||
|
||||
let text = await response.text();
|
||||
text = this.#removeAngularJSResponseData(text);
|
||||
|
||||
const data = JSON.parse(text);
|
||||
|
||||
if (response.ok) {
|
||||
return {
|
||||
if (response.ok) {
|
||||
return {
|
||||
data,
|
||||
status: response.status,
|
||||
};
|
||||
}
|
||||
status: response.status,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
status: response.status,
|
||||
error: data.Message ?? 'An unknown error occurred.',
|
||||
};
|
||||
}
|
||||
return {
|
||||
status: response.status,
|
||||
error: data.Message ?? 'An unknown error occurred.',
|
||||
};
|
||||
}
|
||||
|
||||
async #getErrorText(response: Response): Promise<string> {
|
||||
switch (response.status) {
|
||||
case 400:
|
||||
case 401:
|
||||
return umbLocalizationContext.localize('login_userFailedLogin', undefined, "Oops! We couldn't log you in. Please check your credentials and try again.");
|
||||
async #getErrorText(response: Response): Promise<string> {
|
||||
switch (response.status) {
|
||||
case 400:
|
||||
case 401:
|
||||
return umbLocalizationContext.localize('login_userFailedLogin', undefined, "Oops! We couldn't log you in. Please check your credentials and try again.");
|
||||
|
||||
case 402:
|
||||
return umbLocalizationContext.localize('login_2faText', undefined, 'You have enabled 2-factor authentication and must verify your identity.');
|
||||
case 402:
|
||||
return umbLocalizationContext.localize('login_2faText', undefined, 'You have enabled 2-factor authentication and must verify your identity.');
|
||||
|
||||
case 500:
|
||||
return umbLocalizationContext.localize('errors_receivedErrorFromServer', undefined, 'Received error from server');
|
||||
case 500:
|
||||
return umbLocalizationContext.localize('errors_receivedErrorFromServer', undefined, 'Received error from server');
|
||||
|
||||
default:
|
||||
return response.statusText ?? await umbLocalizationContext.localize('errors_receivedErrorFromServer', undefined, 'Received error from server')
|
||||
}
|
||||
}
|
||||
default:
|
||||
return response.statusText ?? await umbLocalizationContext.localize('errors_receivedErrorFromServer', undefined, 'Received error from server')
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* AngularJS adds a prefix to the response data, which we need to remove
|
||||
*/
|
||||
#removeAngularJSResponseData(text: string) {
|
||||
if (text.startsWith(")]}',\n")) {
|
||||
text = text.split('\n')[1];
|
||||
}
|
||||
/**
|
||||
* AngularJS adds a prefix to the response data, which we need to remove
|
||||
*/
|
||||
#removeAngularJSResponseData(text: string) {
|
||||
if (text.startsWith(")]}',\n")) {
|
||||
text = text.split('\n')[1];
|
||||
}
|
||||
|
||||
return text;
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
async #managementApiLogin(username: string, password: string) {
|
||||
try {
|
||||
const authURLManagementApi = 'management/api/v1/security/back-office/login';
|
||||
const requestManagementApi = new Request(authURLManagementApi, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
username,
|
||||
password,
|
||||
}),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
return await fetch(requestManagementApi);
|
||||
} catch (error) {
|
||||
console.error('Failed to authenticate with the Management API:', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user