blob: a22ed15930f4b4f257d450f9db2c37e51b291ea8 [file]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
import { withRuntimeBasePath } from '../runtimeBasePath';
import { setCsrfToken } from './csrf';
import { UiApiError } from './client';
import { fetchMe } from './me';
import type { UiMe } from './types';
function encodeBasic(username: string, password: string): string {
const bytes = new TextEncoder().encode(`${username}:${password}`);
let binary = '';
for (const byte of bytes) binary += String.fromCharCode(byte);
return btoa(binary);
}
function loginError(status: number, body?: unknown): UiApiError {
if (body && typeof body === 'object' && 'code' in body && 'message' in body && 'requestId' in body) {
const error = body as { code: string; message: string; requestId: string; details?: unknown };
return new UiApiError(status, error);
}
const requestId = 'unknown';
if (status === 429) {
return new UiApiError(status, {
code: 'UI_RATE_LIMITED',
message: 'Too many sign-in attempts. Please try again later.',
requestId,
});
}
if (status === 403) {
return new UiApiError(status, {
code: 'UI_ADMIN_REQUIRED',
message: 'This account is authenticated but is not authorized to use the Doris Web Console.',
requestId,
});
}
if (status === 401) {
return new UiApiError(status, {
code: 'UI_LOGIN_FAILED',
message: 'Sign-in failed. Check the username and password.',
requestId,
});
}
return new UiApiError(status, {
code: 'UI_LOGIN_UNAVAILABLE',
message: 'The Doris FE could not complete sign-in.',
requestId,
});
}
function legacyResponseCode(body: unknown): number | null {
if (!body || typeof body !== 'object' || !('code' in body)) return null;
const code = (body as { code?: unknown }).code;
return typeof code === 'number' ? code : null;
}
export async function login(username: string, password: string): Promise<UiMe> {
let response: Response;
try {
response = await fetch(withRuntimeBasePath('/rest/v1/login'), {
method: 'POST',
headers: {
Accept: 'application/json',
Authorization: `Basic ${encodeBasic(username, password)}`,
},
credentials: 'same-origin',
});
} catch {
throw new UiApiError(0, {
code: 'UI_LOGIN_UNAVAILABLE',
message: 'The Doris FE is unavailable. Check the connection and try again.',
requestId: 'unknown',
});
}
let payload: unknown = null;
try {
payload = await response.json();
} catch {
throw loginError(response.ok ? 500 : response.status);
}
const bodyCode = legacyResponseCode(payload);
const bodyRejected = bodyCode !== null && bodyCode !== 0 && bodyCode !== 200;
if (!response.ok || bodyRejected) {
throw loginError(bodyRejected ? bodyCode : response.status, payload);
}
try {
return await fetchMe();
} catch (error) {
// The legacy login endpoint authenticates every valid Doris user. The UI
// bootstrap performs the ADMIN check; remove the just-created cookie when
// that second step rejects the account.
try {
await logout();
} catch {
setCsrfToken(null);
}
throw error;
}
}
export async function logout(): Promise<void> {
const response = await fetch(withRuntimeBasePath('/rest/v1/logout'), {
method: 'POST',
headers: { Accept: 'application/json' },
credentials: 'same-origin',
});
if (!response.ok) throw loginError(response.status);
setCsrfToken(null);
}