mirror of
https://github.com/bitwarden/browser
synced 2025-12-23 03:33:54 +00:00
Move web to apps/web and bitwarden_license/bit-web
This commit is contained in:
63
apps/web/src/app/common/base.accept.component.ts
Normal file
63
apps/web/src/app/common/base.accept.component.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import { Directive, OnInit } from "@angular/core";
|
||||
import { ActivatedRoute, Router } from "@angular/router";
|
||||
import { first } from "rxjs/operators";
|
||||
|
||||
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||
import { StateService } from "jslib-common/abstractions/state.service";
|
||||
|
||||
@Directive()
|
||||
export abstract class BaseAcceptComponent implements OnInit {
|
||||
loading = true;
|
||||
authed = false;
|
||||
email: string;
|
||||
actionPromise: Promise<any>;
|
||||
|
||||
protected requiredParameters: string[] = [];
|
||||
protected failedShortMessage = "inviteAcceptFailedShort";
|
||||
protected failedMessage = "inviteAcceptFailed";
|
||||
|
||||
constructor(
|
||||
protected router: Router,
|
||||
protected platformUtilService: PlatformUtilsService,
|
||||
protected i18nService: I18nService,
|
||||
protected route: ActivatedRoute,
|
||||
protected stateService: StateService
|
||||
) {}
|
||||
|
||||
abstract authedHandler(qParams: any): Promise<void>;
|
||||
abstract unauthedHandler(qParams: any): Promise<void>;
|
||||
|
||||
ngOnInit() {
|
||||
this.route.queryParams.pipe(first()).subscribe(async (qParams) => {
|
||||
let error = this.requiredParameters.some((e) => qParams?.[e] == null || qParams[e] === "");
|
||||
let errorMessage: string = null;
|
||||
if (!error) {
|
||||
this.authed = await this.stateService.getIsAuthenticated();
|
||||
|
||||
if (this.authed) {
|
||||
try {
|
||||
await this.authedHandler(qParams);
|
||||
} catch (e) {
|
||||
error = true;
|
||||
errorMessage = e.message;
|
||||
}
|
||||
} else {
|
||||
this.email = qParams.email;
|
||||
await this.unauthedHandler(qParams);
|
||||
}
|
||||
}
|
||||
|
||||
if (error) {
|
||||
const message =
|
||||
errorMessage != null
|
||||
? this.i18nService.t(this.failedShortMessage, errorMessage)
|
||||
: this.i18nService.t(this.failedMessage);
|
||||
this.platformUtilService.showToast("error", null, message, { timeout: 10000 });
|
||||
this.router.navigate(["/"]);
|
||||
}
|
||||
|
||||
this.loading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
178
apps/web/src/app/common/base.events.component.ts
Normal file
178
apps/web/src/app/common/base.events.component.ts
Normal file
@@ -0,0 +1,178 @@
|
||||
import { Directive } from "@angular/core";
|
||||
|
||||
import { ExportService } from "jslib-common/abstractions/export.service";
|
||||
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||
import { LogService } from "jslib-common/abstractions/log.service";
|
||||
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||
import { EventResponse } from "jslib-common/models/response/eventResponse";
|
||||
import { ListResponse } from "jslib-common/models/response/listResponse";
|
||||
import { EventView } from "jslib-common/models/view/eventView";
|
||||
|
||||
import { EventService } from "src/app/services/event.service";
|
||||
|
||||
@Directive()
|
||||
export abstract class BaseEventsComponent {
|
||||
loading = true;
|
||||
loaded = false;
|
||||
events: EventView[];
|
||||
start: string;
|
||||
end: string;
|
||||
dirtyDates = true;
|
||||
continuationToken: string;
|
||||
refreshPromise: Promise<any>;
|
||||
exportPromise: Promise<any>;
|
||||
morePromise: Promise<any>;
|
||||
|
||||
abstract readonly exportFileName: string;
|
||||
|
||||
constructor(
|
||||
protected eventService: EventService,
|
||||
protected i18nService: I18nService,
|
||||
protected exportService: ExportService,
|
||||
protected platformUtilsService: PlatformUtilsService,
|
||||
protected logService: LogService
|
||||
) {
|
||||
const defaultDates = this.eventService.getDefaultDateFilters();
|
||||
this.start = defaultDates[0];
|
||||
this.end = defaultDates[1];
|
||||
}
|
||||
|
||||
async exportEvents() {
|
||||
if (this.appApiPromiseUnfulfilled() || this.dirtyDates) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.loading = true;
|
||||
|
||||
const dates = this.parseDates();
|
||||
if (dates == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
this.exportPromise = this.export(dates[0], dates[1]);
|
||||
|
||||
await this.exportPromise;
|
||||
} catch (e) {
|
||||
this.logService.error(`Handled exception: ${e}`);
|
||||
}
|
||||
|
||||
this.exportPromise = null;
|
||||
this.loading = false;
|
||||
}
|
||||
|
||||
async loadEvents(clearExisting: boolean) {
|
||||
if (this.appApiPromiseUnfulfilled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const dates = this.parseDates();
|
||||
if (dates == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.loading = true;
|
||||
let events: EventView[] = [];
|
||||
try {
|
||||
const promise = this.loadAndParseEvents(
|
||||
dates[0],
|
||||
dates[1],
|
||||
clearExisting ? null : this.continuationToken
|
||||
);
|
||||
if (clearExisting) {
|
||||
this.refreshPromise = promise;
|
||||
} else {
|
||||
this.morePromise = promise;
|
||||
}
|
||||
const result = await promise;
|
||||
this.continuationToken = result.continuationToken;
|
||||
events = result.events;
|
||||
} catch (e) {
|
||||
this.logService.error(`Handled exception: ${e}`);
|
||||
}
|
||||
|
||||
if (!clearExisting && this.events != null && this.events.length > 0) {
|
||||
this.events = this.events.concat(events);
|
||||
} else {
|
||||
this.events = events;
|
||||
}
|
||||
|
||||
this.dirtyDates = false;
|
||||
this.loading = false;
|
||||
this.morePromise = null;
|
||||
this.refreshPromise = null;
|
||||
}
|
||||
|
||||
protected abstract requestEvents(
|
||||
startDate: string,
|
||||
endDate: string,
|
||||
continuationToken: string
|
||||
): Promise<ListResponse<EventResponse>>;
|
||||
protected abstract getUserName(r: EventResponse, userId: string): { name: string; email: string };
|
||||
|
||||
protected async loadAndParseEvents(
|
||||
startDate: string,
|
||||
endDate: string,
|
||||
continuationToken: string
|
||||
) {
|
||||
const response = await this.requestEvents(startDate, endDate, continuationToken);
|
||||
|
||||
const events = await Promise.all(
|
||||
response.data.map(async (r) => {
|
||||
const userId = r.actingUserId == null ? r.userId : r.actingUserId;
|
||||
const eventInfo = await this.eventService.getEventInfo(r);
|
||||
const user = this.getUserName(r, userId);
|
||||
const userName = user != null ? user.name : this.i18nService.t("unknown");
|
||||
|
||||
return new EventView({
|
||||
message: eventInfo.message,
|
||||
humanReadableMessage: eventInfo.humanReadableMessage,
|
||||
appIcon: eventInfo.appIcon,
|
||||
appName: eventInfo.appName,
|
||||
userId: userId,
|
||||
userName: r.installationId != null ? `Installation: ${r.installationId}` : userName,
|
||||
userEmail: user != null ? user.email : "",
|
||||
date: r.date,
|
||||
ip: r.ipAddress,
|
||||
type: r.type,
|
||||
installationId: r.installationId,
|
||||
});
|
||||
})
|
||||
);
|
||||
return { continuationToken: response.continuationToken, events: events };
|
||||
}
|
||||
|
||||
protected parseDates() {
|
||||
let dates: string[] = null;
|
||||
try {
|
||||
dates = this.eventService.formatDateFilters(this.start, this.end);
|
||||
} catch (e) {
|
||||
this.platformUtilsService.showToast(
|
||||
"error",
|
||||
this.i18nService.t("errorOccurred"),
|
||||
this.i18nService.t("invalidDateRange")
|
||||
);
|
||||
return null;
|
||||
}
|
||||
return dates;
|
||||
}
|
||||
|
||||
protected appApiPromiseUnfulfilled() {
|
||||
return this.refreshPromise != null || this.morePromise != null || this.exportPromise != null;
|
||||
}
|
||||
|
||||
private async export(start: string, end: string) {
|
||||
let continuationToken = this.continuationToken;
|
||||
let events = [].concat(this.events);
|
||||
|
||||
while (continuationToken != null) {
|
||||
const result = await this.loadAndParseEvents(start, end, continuationToken);
|
||||
continuationToken = result.continuationToken;
|
||||
events = events.concat(result.events);
|
||||
}
|
||||
|
||||
const data = await this.exportService.getEventExport(events);
|
||||
const fileName = this.exportService.getFileName(this.exportFileName, "csv");
|
||||
this.platformUtilsService.saveFile(window, data, { type: "text/plain" }, fileName);
|
||||
}
|
||||
}
|
||||
345
apps/web/src/app/common/base.people.component.ts
Normal file
345
apps/web/src/app/common/base.people.component.ts
Normal file
@@ -0,0 +1,345 @@
|
||||
import { Directive, ViewChild, ViewContainerRef } from "@angular/core";
|
||||
|
||||
import { SearchPipe } from "jslib-angular/pipes/search.pipe";
|
||||
import { UserNamePipe } from "jslib-angular/pipes/user-name.pipe";
|
||||
import { ModalService } from "jslib-angular/services/modal.service";
|
||||
import { ValidationService } from "jslib-angular/services/validation.service";
|
||||
import { ApiService } from "jslib-common/abstractions/api.service";
|
||||
import { CryptoService } from "jslib-common/abstractions/crypto.service";
|
||||
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||
import { LogService } from "jslib-common/abstractions/log.service";
|
||||
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||
import { SearchService } from "jslib-common/abstractions/search.service";
|
||||
import { StateService } from "jslib-common/abstractions/state.service";
|
||||
import { OrganizationUserStatusType } from "jslib-common/enums/organizationUserStatusType";
|
||||
import { OrganizationUserType } from "jslib-common/enums/organizationUserType";
|
||||
import { ProviderUserStatusType } from "jslib-common/enums/providerUserStatusType";
|
||||
import { ProviderUserType } from "jslib-common/enums/providerUserType";
|
||||
import { Utils } from "jslib-common/misc/utils";
|
||||
import { ListResponse } from "jslib-common/models/response/listResponse";
|
||||
import { OrganizationUserUserDetailsResponse } from "jslib-common/models/response/organizationUserResponse";
|
||||
import { ProviderUserUserDetailsResponse } from "jslib-common/models/response/provider/providerUserResponse";
|
||||
|
||||
import { UserConfirmComponent } from "../organizations/manage/user-confirm.component";
|
||||
|
||||
type StatusType = OrganizationUserStatusType | ProviderUserStatusType;
|
||||
|
||||
const MaxCheckedCount = 500;
|
||||
|
||||
@Directive()
|
||||
export abstract class BasePeopleComponent<
|
||||
UserType extends ProviderUserUserDetailsResponse | OrganizationUserUserDetailsResponse
|
||||
> {
|
||||
@ViewChild("confirmTemplate", { read: ViewContainerRef, static: true })
|
||||
confirmModalRef: ViewContainerRef;
|
||||
|
||||
get allCount() {
|
||||
return this.allUsers != null ? this.allUsers.length : 0;
|
||||
}
|
||||
|
||||
get invitedCount() {
|
||||
return this.statusMap.has(this.userStatusType.Invited)
|
||||
? this.statusMap.get(this.userStatusType.Invited).length
|
||||
: 0;
|
||||
}
|
||||
|
||||
get acceptedCount() {
|
||||
return this.statusMap.has(this.userStatusType.Accepted)
|
||||
? this.statusMap.get(this.userStatusType.Accepted).length
|
||||
: 0;
|
||||
}
|
||||
|
||||
get confirmedCount() {
|
||||
return this.statusMap.has(this.userStatusType.Confirmed)
|
||||
? this.statusMap.get(this.userStatusType.Confirmed).length
|
||||
: 0;
|
||||
}
|
||||
|
||||
get showConfirmUsers(): boolean {
|
||||
return (
|
||||
this.allUsers != null &&
|
||||
this.statusMap != null &&
|
||||
this.allUsers.length > 1 &&
|
||||
this.confirmedCount > 0 &&
|
||||
this.confirmedCount < 3 &&
|
||||
this.acceptedCount > 0
|
||||
);
|
||||
}
|
||||
|
||||
get showBulkConfirmUsers(): boolean {
|
||||
return this.acceptedCount > 0;
|
||||
}
|
||||
|
||||
abstract userType: typeof OrganizationUserType | typeof ProviderUserType;
|
||||
abstract userStatusType: typeof OrganizationUserStatusType | typeof ProviderUserStatusType;
|
||||
|
||||
loading = true;
|
||||
statusMap = new Map<StatusType, UserType[]>();
|
||||
status: StatusType;
|
||||
users: UserType[] = [];
|
||||
pagedUsers: UserType[] = [];
|
||||
searchText: string;
|
||||
actionPromise: Promise<any>;
|
||||
|
||||
protected allUsers: UserType[] = [];
|
||||
|
||||
protected didScroll = false;
|
||||
protected pageSize = 100;
|
||||
|
||||
private pagedUsersCount = 0;
|
||||
|
||||
constructor(
|
||||
protected apiService: ApiService,
|
||||
private searchService: SearchService,
|
||||
protected i18nService: I18nService,
|
||||
protected platformUtilsService: PlatformUtilsService,
|
||||
protected cryptoService: CryptoService,
|
||||
protected validationService: ValidationService,
|
||||
protected modalService: ModalService,
|
||||
private logService: LogService,
|
||||
private searchPipe: SearchPipe,
|
||||
protected userNamePipe: UserNamePipe,
|
||||
protected stateService: StateService
|
||||
) {}
|
||||
|
||||
abstract edit(user: UserType): void;
|
||||
abstract getUsers(): Promise<ListResponse<UserType>>;
|
||||
abstract deleteUser(id: string): Promise<any>;
|
||||
abstract reinviteUser(id: string): Promise<any>;
|
||||
abstract confirmUser(user: UserType, publicKey: Uint8Array): Promise<any>;
|
||||
|
||||
async load() {
|
||||
const response = await this.getUsers();
|
||||
this.statusMap.clear();
|
||||
for (const status of Utils.iterateEnum(this.userStatusType)) {
|
||||
this.statusMap.set(status, []);
|
||||
}
|
||||
|
||||
this.allUsers = response.data != null && response.data.length > 0 ? response.data : [];
|
||||
this.allUsers.sort(Utils.getSortFunction(this.i18nService, "email"));
|
||||
this.allUsers.forEach((u) => {
|
||||
if (!this.statusMap.has(u.status)) {
|
||||
this.statusMap.set(u.status, [u]);
|
||||
} else {
|
||||
this.statusMap.get(u.status).push(u);
|
||||
}
|
||||
});
|
||||
this.filter(this.status);
|
||||
this.loading = false;
|
||||
}
|
||||
|
||||
filter(status: StatusType) {
|
||||
this.status = status;
|
||||
if (this.status != null) {
|
||||
this.users = this.statusMap.get(this.status);
|
||||
} else {
|
||||
this.users = this.allUsers;
|
||||
}
|
||||
// Reset checkbox selecton
|
||||
this.selectAll(false);
|
||||
this.resetPaging();
|
||||
}
|
||||
|
||||
loadMore() {
|
||||
if (!this.users || this.users.length <= this.pageSize) {
|
||||
return;
|
||||
}
|
||||
const pagedLength = this.pagedUsers.length;
|
||||
let pagedSize = this.pageSize;
|
||||
if (pagedLength === 0 && this.pagedUsersCount > this.pageSize) {
|
||||
pagedSize = this.pagedUsersCount;
|
||||
}
|
||||
if (this.users.length > pagedLength) {
|
||||
this.pagedUsers = this.pagedUsers.concat(
|
||||
this.users.slice(pagedLength, pagedLength + pagedSize)
|
||||
);
|
||||
}
|
||||
this.pagedUsersCount = this.pagedUsers.length;
|
||||
this.didScroll = this.pagedUsers.length > this.pageSize;
|
||||
}
|
||||
|
||||
checkUser(user: OrganizationUserUserDetailsResponse, select?: boolean) {
|
||||
(user as any).checked = select == null ? !(user as any).checked : select;
|
||||
}
|
||||
|
||||
selectAll(select: boolean) {
|
||||
if (select) {
|
||||
this.selectAll(false);
|
||||
}
|
||||
|
||||
const filteredUsers = this.searchPipe.transform(
|
||||
this.users,
|
||||
this.searchText,
|
||||
"name",
|
||||
"email",
|
||||
"id"
|
||||
);
|
||||
|
||||
const selectCount =
|
||||
select && filteredUsers.length > MaxCheckedCount ? MaxCheckedCount : filteredUsers.length;
|
||||
for (let i = 0; i < selectCount; i++) {
|
||||
this.checkUser(filteredUsers[i], select);
|
||||
}
|
||||
}
|
||||
|
||||
async resetPaging() {
|
||||
this.pagedUsers = [];
|
||||
this.loadMore();
|
||||
}
|
||||
|
||||
invite() {
|
||||
this.edit(null);
|
||||
}
|
||||
|
||||
async remove(user: UserType) {
|
||||
const confirmed = await this.platformUtilsService.showDialog(
|
||||
this.deleteWarningMessage(user),
|
||||
this.userNamePipe.transform(user),
|
||||
this.i18nService.t("yes"),
|
||||
this.i18nService.t("no"),
|
||||
"warning"
|
||||
);
|
||||
|
||||
if (!confirmed) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.actionPromise = this.deleteUser(user.id);
|
||||
try {
|
||||
await this.actionPromise;
|
||||
this.platformUtilsService.showToast(
|
||||
"success",
|
||||
null,
|
||||
this.i18nService.t("removedUserId", this.userNamePipe.transform(user))
|
||||
);
|
||||
this.removeUser(user);
|
||||
} catch (e) {
|
||||
this.validationService.showError(e);
|
||||
}
|
||||
this.actionPromise = null;
|
||||
}
|
||||
|
||||
async reinvite(user: UserType) {
|
||||
if (this.actionPromise != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.actionPromise = this.reinviteUser(user.id);
|
||||
try {
|
||||
await this.actionPromise;
|
||||
this.platformUtilsService.showToast(
|
||||
"success",
|
||||
null,
|
||||
this.i18nService.t("hasBeenReinvited", this.userNamePipe.transform(user))
|
||||
);
|
||||
} catch (e) {
|
||||
this.validationService.showError(e);
|
||||
}
|
||||
this.actionPromise = null;
|
||||
}
|
||||
|
||||
async confirm(user: UserType) {
|
||||
function updateUser(self: BasePeopleComponent<UserType>) {
|
||||
user.status = self.userStatusType.Confirmed;
|
||||
const mapIndex = self.statusMap.get(self.userStatusType.Accepted).indexOf(user);
|
||||
if (mapIndex > -1) {
|
||||
self.statusMap.get(self.userStatusType.Accepted).splice(mapIndex, 1);
|
||||
self.statusMap.get(self.userStatusType.Confirmed).push(user);
|
||||
}
|
||||
}
|
||||
|
||||
const confirmUser = async (publicKey: Uint8Array) => {
|
||||
try {
|
||||
this.actionPromise = this.confirmUser(user, publicKey);
|
||||
await this.actionPromise;
|
||||
updateUser(this);
|
||||
this.platformUtilsService.showToast(
|
||||
"success",
|
||||
null,
|
||||
this.i18nService.t("hasBeenConfirmed", this.userNamePipe.transform(user))
|
||||
);
|
||||
} catch (e) {
|
||||
this.validationService.showError(e);
|
||||
throw e;
|
||||
} finally {
|
||||
this.actionPromise = null;
|
||||
}
|
||||
};
|
||||
|
||||
if (this.actionPromise != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const publicKeyResponse = await this.apiService.getUserPublicKey(user.userId);
|
||||
const publicKey = Utils.fromB64ToArray(publicKeyResponse.publicKey);
|
||||
|
||||
const autoConfirm = await this.stateService.getAutoConfirmFingerPrints();
|
||||
if (autoConfirm == null || !autoConfirm) {
|
||||
const [modal] = await this.modalService.openViewRef(
|
||||
UserConfirmComponent,
|
||||
this.confirmModalRef,
|
||||
(comp) => {
|
||||
comp.name = this.userNamePipe.transform(user);
|
||||
comp.userId = user != null ? user.userId : null;
|
||||
comp.publicKey = publicKey;
|
||||
comp.onConfirmedUser.subscribe(async () => {
|
||||
try {
|
||||
comp.formPromise = confirmUser(publicKey);
|
||||
await comp.formPromise;
|
||||
modal.close();
|
||||
} catch (e) {
|
||||
this.logService.error(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const fingerprint = await this.cryptoService.getFingerprint(user.userId, publicKey.buffer);
|
||||
this.logService.info(`User's fingerprint: ${fingerprint.join("-")}`);
|
||||
} catch (e) {
|
||||
this.logService.error(e);
|
||||
}
|
||||
await confirmUser(publicKey);
|
||||
} catch (e) {
|
||||
this.logService.error(`Handled exception: ${e}`);
|
||||
}
|
||||
}
|
||||
|
||||
isSearching() {
|
||||
return this.searchService.isSearchable(this.searchText);
|
||||
}
|
||||
|
||||
isPaging() {
|
||||
const searching = this.isSearching();
|
||||
if (searching && this.didScroll) {
|
||||
this.resetPaging();
|
||||
}
|
||||
return !searching && this.users && this.users.length > this.pageSize;
|
||||
}
|
||||
|
||||
protected deleteWarningMessage(user: UserType): string {
|
||||
return this.i18nService.t("removeUserConfirmation");
|
||||
}
|
||||
|
||||
protected getCheckedUsers() {
|
||||
return this.users.filter((u) => (u as any).checked);
|
||||
}
|
||||
|
||||
protected removeUser(user: UserType) {
|
||||
let index = this.users.indexOf(user);
|
||||
if (index > -1) {
|
||||
this.users.splice(index, 1);
|
||||
this.resetPaging();
|
||||
}
|
||||
if (this.statusMap.has(user.status)) {
|
||||
index = this.statusMap.get(user.status).indexOf(user);
|
||||
if (index > -1) {
|
||||
this.statusMap.get(user.status).splice(index, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user