mirror of
https://github.com/bitwarden/browser.git
synced 2025-03-12 13:39:14 +01:00
Use organization api key for auth (#382)
* Create UserService for Api Keys * Limit scope request for organization keys * Expose necessary services for org key-based auth service * Linter fixes * Add public import models Since public import is tied tightly to the private api, constructors are provided to maintain coupling in case of changes * Do not parallelize file access This storage is sometims backed by lowdb files. Parallel writes can cause issues. * Match file name to class * Serialize storageService promises * Prefer multiple awaits to .then chains * Linter fixes
This commit is contained in:
parent
73ec484b17
commit
79e6d012c5
@ -31,6 +31,7 @@ import { ImportOrganizationCiphersRequest } from '../models/request/importOrgani
|
||||
import { KdfRequest } from '../models/request/kdfRequest';
|
||||
import { KeysRequest } from '../models/request/keysRequest';
|
||||
import { OrganizationCreateRequest } from '../models/request/organizationCreateRequest';
|
||||
import { OrganizationImportRequest } from '../models/request/organizationImportRequest';
|
||||
import { OrganizationKeysRequest } from '../models/request/organizationKeysRequest';
|
||||
import { OrganizationTaxInfoUpdateRequest } from '../models/request/organizationTaxInfoUpdateRequest';
|
||||
import { OrganizationUpdateRequest } from '../models/request/organizationUpdateRequest';
|
||||
@ -296,6 +297,7 @@ export abstract class ApiService {
|
||||
|
||||
getSync: () => Promise<SyncResponse>;
|
||||
postImportDirectory: (organizationId: string, request: ImportDirectoryRequest) => Promise<any>;
|
||||
postPublicImportDirectory: (request: OrganizationImportRequest) => Promise<any>;
|
||||
|
||||
getSettingsDomains: () => Promise<DomainsResponse>;
|
||||
putSettingsDomains: (request: UpdateDomainsRequest) => Promise<DomainsResponse>;
|
||||
|
7
src/abstractions/apiKey.service.ts
Normal file
7
src/abstractions/apiKey.service.ts
Normal file
@ -0,0 +1,7 @@
|
||||
export abstract class ApiKeyService {
|
||||
setInformation: (clientId: string) => Promise<any>;
|
||||
clear: () => Promise<any>;
|
||||
getEntityType: () => Promise<string>;
|
||||
getEntityId: () => Promise<string>;
|
||||
isAuthenticated: () => Promise<boolean>;
|
||||
}
|
19
src/models/request/organizationImportGroupRequest.ts
Normal file
19
src/models/request/organizationImportGroupRequest.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import { ImportDirectoryRequestGroup } from './importDirectoryRequestGroup';
|
||||
|
||||
export class OrganizationImportGroupRequest {
|
||||
name: string;
|
||||
externalId: string;
|
||||
memberExternalIds: string[];
|
||||
|
||||
constructor(model: Required<OrganizationImportGroupRequest> | ImportDirectoryRequestGroup) {
|
||||
this.name = model.name;
|
||||
this.externalId = model.externalId;
|
||||
|
||||
if (model instanceof ImportDirectoryRequestGroup) {
|
||||
this.memberExternalIds = model.users;
|
||||
}
|
||||
else {
|
||||
this.memberExternalIds = model.memberExternalIds;
|
||||
}
|
||||
}
|
||||
}
|
13
src/models/request/organizationImportMemberRequest.ts
Normal file
13
src/models/request/organizationImportMemberRequest.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import { ImportDirectoryRequestUser } from './importDirectoryRequestUser';
|
||||
|
||||
export class OrganizationImportMemberRequest {
|
||||
email: string;
|
||||
externalId: string;
|
||||
deleted: boolean;
|
||||
|
||||
constructor(model: Required<OrganizationImportMemberRequest> | ImportDirectoryRequestUser) {
|
||||
this.email = model.email;
|
||||
this.externalId = model.externalId;
|
||||
this.deleted = model.deleted;
|
||||
}
|
||||
}
|
25
src/models/request/organizationImportRequest.ts
Normal file
25
src/models/request/organizationImportRequest.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import { ImportDirectoryRequest } from './importDirectoryRequest';
|
||||
import { OrganizationImportGroupRequest } from './organizationImportGroupRequest';
|
||||
import { OrganizationImportMemberRequest } from './organizationImportMemberRequest';
|
||||
|
||||
export class OrganizationImportRequest {
|
||||
groups: OrganizationImportGroupRequest[] = [];
|
||||
members: OrganizationImportMemberRequest[] = [];
|
||||
overwriteExisting: boolean = false;
|
||||
largeImport: boolean = false;
|
||||
|
||||
constructor(model: {
|
||||
groups: Required<OrganizationImportGroupRequest>[],
|
||||
users: Required<OrganizationImportMemberRequest>[], overwriteExisting: boolean, largeImport: boolean;
|
||||
} | ImportDirectoryRequest) {
|
||||
if (model instanceof ImportDirectoryRequest) {
|
||||
this.groups = model.groups.map(g => new OrganizationImportGroupRequest(g));
|
||||
this.members = model.users.map(u => new OrganizationImportMemberRequest(u));
|
||||
} else {
|
||||
this.groups = model.groups.map(g => new OrganizationImportGroupRequest(g));
|
||||
this.members = model.users.map(u => new OrganizationImportMemberRequest(u));
|
||||
}
|
||||
this.overwriteExisting = model.overwriteExisting;
|
||||
this.largeImport = model.largeImport;
|
||||
}
|
||||
}
|
@ -41,7 +41,7 @@ export class TokenRequest {
|
||||
};
|
||||
|
||||
if (this.clientSecret != null) {
|
||||
obj.scope = 'api';
|
||||
obj.scope = clientId.startsWith('organization') ? 'api.organization' : 'api';
|
||||
obj.grant_type = 'client_credentials';
|
||||
obj.client_secret = this.clientSecret;
|
||||
} else if (this.masterPasswordHash != null && this.email != null) {
|
||||
|
@ -35,6 +35,7 @@ import { ImportOrganizationCiphersRequest } from '../models/request/importOrgani
|
||||
import { KdfRequest } from '../models/request/kdfRequest';
|
||||
import { KeysRequest } from '../models/request/keysRequest';
|
||||
import { OrganizationCreateRequest } from '../models/request/organizationCreateRequest';
|
||||
import { OrganizationImportRequest } from '../models/request/organizationImportRequest';
|
||||
import { OrganizationKeysRequest } from '../models/request/organizationKeysRequest';
|
||||
import { OrganizationTaxInfoUpdateRequest } from '../models/request/organizationTaxInfoUpdateRequest';
|
||||
import { OrganizationUpdateRequest } from '../models/request/organizationUpdateRequest';
|
||||
@ -868,6 +869,10 @@ export class ApiService implements ApiServiceAbstraction {
|
||||
return this.send('POST', '/organizations/' + organizationId + '/import', request, true, false);
|
||||
}
|
||||
|
||||
async postPublicImportDirectory(request: OrganizationImportRequest): Promise<any> {
|
||||
return this.send('POST', '/public/organization/import', request, true, false);
|
||||
}
|
||||
|
||||
async getTaxRates(): Promise<ListResponse<TaxRateResponse>> {
|
||||
const r = await this.send('GET', '/plans/sales-tax-rates/', null, true, true);
|
||||
return new ListResponse(r, TaxRateResponse);
|
||||
|
67
src/services/apiKey.service.ts
Normal file
67
src/services/apiKey.service.ts
Normal file
@ -0,0 +1,67 @@
|
||||
import { ApiKeyService as ApiKeyServiceAbstraction } from '../abstractions/apiKey.service';
|
||||
import { StorageService } from '../abstractions/storage.service';
|
||||
import { TokenService } from '../abstractions/token.service';
|
||||
|
||||
import { Utils } from '../misc/utils';
|
||||
|
||||
const Keys = {
|
||||
clientId: 'clientId',
|
||||
entityType: 'entityType',
|
||||
entityId: 'entityId',
|
||||
};
|
||||
|
||||
|
||||
export class ApiKeyService implements ApiKeyServiceAbstraction {
|
||||
private clientId: string;
|
||||
private entityType: string;
|
||||
private entityId: string;
|
||||
|
||||
constructor(private tokenService: TokenService, private storageService: StorageService) { }
|
||||
|
||||
async setInformation(clientId: string) {
|
||||
this.clientId = clientId;
|
||||
const idParts = clientId.split('.');
|
||||
|
||||
if (idParts.length !== 2 || !Utils.isGuid(idParts[1])) {
|
||||
throw Error('Invalid clientId');
|
||||
}
|
||||
this.entityType = idParts[0];
|
||||
this.entityId = idParts[1];
|
||||
|
||||
await this.storageService.save(Keys.clientId, this.clientId);
|
||||
await this.storageService.save(Keys.entityId, this.entityId);
|
||||
await this.storageService.save(Keys.entityType, this.entityType);
|
||||
}
|
||||
|
||||
async getEntityType(): Promise<string> {
|
||||
if (this.entityType == null) {
|
||||
this.entityType = await this.storageService.get<string>(Keys.entityType);
|
||||
}
|
||||
return this.entityType;
|
||||
}
|
||||
|
||||
async getEntityId(): Promise<string> {
|
||||
if (this.entityId == null) {
|
||||
this.entityId = await this.storageService.get<string>(Keys.entityId);
|
||||
}
|
||||
return this.entityId;
|
||||
}
|
||||
|
||||
async clear(): Promise<any> {
|
||||
await this.storageService.remove(Keys.clientId);
|
||||
await this.storageService.remove(Keys.entityId);
|
||||
await this.storageService.remove(Keys.entityType);
|
||||
|
||||
this.clientId = this.entityId = this.entityType = null;
|
||||
}
|
||||
|
||||
async isAuthenticated(): Promise<boolean> {
|
||||
const token = await this.tokenService.getToken();
|
||||
if (token == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const entityId = await this.getEntityId();
|
||||
return entityId != null;
|
||||
}
|
||||
}
|
@ -88,10 +88,10 @@ export class AuthService implements AuthServiceAbstraction {
|
||||
|
||||
private key: SymmetricCryptoKey;
|
||||
|
||||
constructor(private cryptoService: CryptoService, private apiService: ApiService,
|
||||
private userService: UserService, private tokenService: TokenService,
|
||||
private appIdService: AppIdService, private i18nService: I18nService,
|
||||
private platformUtilsService: PlatformUtilsService, private messagingService: MessagingService,
|
||||
constructor(private cryptoService: CryptoService, protected apiService: ApiService,
|
||||
private userService: UserService, protected tokenService: TokenService,
|
||||
protected appIdService: AppIdService, private i18nService: I18nService,
|
||||
protected platformUtilsService: PlatformUtilsService, private messagingService: MessagingService,
|
||||
private vaultTimeoutService: VaultTimeoutService, private logService: LogService,
|
||||
private setCryptoKeys = true) {
|
||||
}
|
||||
|
@ -294,15 +294,13 @@ export class CryptoService implements CryptoServiceAbstraction {
|
||||
return this.storageService.remove(ConstantsService.pinProtectedKey);
|
||||
}
|
||||
|
||||
clearKeys(): Promise<any> {
|
||||
return Promise.all([
|
||||
this.clearKey(),
|
||||
this.clearKeyHash(),
|
||||
this.clearOrgKeys(),
|
||||
this.clearEncKey(),
|
||||
this.clearKeyPair(),
|
||||
this.clearPinProtectedKey(),
|
||||
]);
|
||||
async clearKeys(): Promise<any> {
|
||||
await this.clearEncKey();
|
||||
await this.clearKeyHash();
|
||||
await this.clearOrgKeys();
|
||||
await this.clearEncKey();
|
||||
await this.clearKeyPair();
|
||||
await this.clearPinProtectedKey();
|
||||
}
|
||||
|
||||
async toggleKey(): Promise<any> {
|
||||
|
@ -19,11 +19,9 @@ export class TokenService implements TokenServiceAbstraction {
|
||||
constructor(private storageService: StorageService) {
|
||||
}
|
||||
|
||||
setTokens(accessToken: string, refreshToken: string): Promise<any> {
|
||||
return Promise.all([
|
||||
this.setToken(accessToken),
|
||||
this.setRefreshToken(refreshToken),
|
||||
]);
|
||||
async setTokens(accessToken: string, refreshToken: string): Promise<any> {
|
||||
await this.setToken(accessToken);
|
||||
await this.setRefreshToken(refreshToken);
|
||||
}
|
||||
|
||||
async setToken(token: string): Promise<any> {
|
||||
@ -96,15 +94,13 @@ export class TokenService implements TokenServiceAbstraction {
|
||||
return this.storageService.remove(Keys.twoFactorTokenPrefix + email);
|
||||
}
|
||||
|
||||
clearToken(): Promise<any> {
|
||||
async clearToken(): Promise<any> {
|
||||
this.token = null;
|
||||
this.decodedToken = null;
|
||||
this.refreshToken = null;
|
||||
|
||||
return Promise.all([
|
||||
this.storageService.remove(Keys.accessToken),
|
||||
this.storageService.remove(Keys.refreshToken),
|
||||
]);
|
||||
await this.storageService.remove(Keys.accessToken);
|
||||
await this.storageService.remove(Keys.refreshToken);
|
||||
}
|
||||
|
||||
// jwthelper methods
|
||||
|
@ -27,18 +27,16 @@ export class UserService implements UserServiceAbstraction {
|
||||
|
||||
constructor(private tokenService: TokenService, private storageService: StorageService) { }
|
||||
|
||||
setInformation(userId: string, email: string, kdf: KdfType, kdfIterations: number): Promise<any> {
|
||||
async setInformation(userId: string, email: string, kdf: KdfType, kdfIterations: number): Promise<any> {
|
||||
this.email = email;
|
||||
this.userId = userId;
|
||||
this.kdf = kdf;
|
||||
this.kdfIterations = kdfIterations;
|
||||
|
||||
return Promise.all([
|
||||
this.storageService.save(Keys.userEmail, email),
|
||||
this.storageService.save(Keys.userId, userId),
|
||||
this.storageService.save(Keys.kdf, kdf),
|
||||
this.storageService.save(Keys.kdfIterations, kdfIterations),
|
||||
]);
|
||||
await this.storageService.save(Keys.userEmail, email);
|
||||
await this.storageService.save(Keys.userId, userId);
|
||||
await this.storageService.save(Keys.kdf, kdf);
|
||||
await this.storageService.save(Keys.kdfIterations, kdfIterations);
|
||||
}
|
||||
|
||||
setSecurityStamp(stamp: string): Promise<any> {
|
||||
@ -96,14 +94,12 @@ export class UserService implements UserServiceAbstraction {
|
||||
async clear(): Promise<any> {
|
||||
const userId = await this.getUserId();
|
||||
|
||||
await Promise.all([
|
||||
this.storageService.remove(Keys.userId),
|
||||
this.storageService.remove(Keys.userEmail),
|
||||
this.storageService.remove(Keys.stamp),
|
||||
this.storageService.remove(Keys.kdf),
|
||||
this.storageService.remove(Keys.kdfIterations),
|
||||
this.clearOrganizations(userId),
|
||||
]);
|
||||
await this.storageService.remove(Keys.userId);
|
||||
await this.storageService.remove(Keys.userEmail);
|
||||
await this.storageService.remove(Keys.stamp);
|
||||
await this.storageService.remove(Keys.kdf);
|
||||
await this.storageService.remove(Keys.kdfIterations);
|
||||
await this.clearOrganizations(userId);
|
||||
|
||||
this.userId = this.email = this.stamp = null;
|
||||
this.kdf = null;
|
||||
|
@ -113,12 +113,10 @@ export class VaultTimeoutService implements VaultTimeoutServiceAbstraction {
|
||||
}
|
||||
}
|
||||
|
||||
await Promise.all([
|
||||
this.cryptoService.clearKey(),
|
||||
this.cryptoService.clearOrgKeys(true),
|
||||
this.cryptoService.clearKeyPair(true),
|
||||
this.cryptoService.clearEncKey(true),
|
||||
]);
|
||||
await this.cryptoService.clearKey();
|
||||
await this.cryptoService.clearOrgKeys(true);
|
||||
await this.cryptoService.clearKeyPair(true);
|
||||
await this.cryptoService.clearEncKey(true);
|
||||
|
||||
this.folderService.clearCache();
|
||||
this.cipherService.clearCache();
|
||||
|
Loading…
Reference in New Issue
Block a user