mirror of
https://github.com/bitwarden/browser
synced 2025-12-10 21:33:27 +00:00
* PM-5263 - Token Service state migration - (1) Got key and state definitions setup (2) Ported over core state service getTimeoutBasedStorageOptions method logic into local determineStorageLocation method (3) Updated majority of methods to use state provider state * PM-5263 - StateSvc - add TODO to remove timeoutBasedStorageOptions + other state methods after migration code complete. * PM-5263 - TokenSvc - ClearToken method - (1) Update signature to remove user id as it wasn't used and it simplifies the new state provider implementation (2) Convert away from state svc to state provider state. * PM-5263 - TokenService - update deps - WIP on circular dep issues. * PM-5263 - To resolve circular dep issues between VaultTimeoutSettingsSvc and TokenService: (1) For writes, require callers to pass in vault timeout data (2) For reads, we can just check both locations. This approach has 1 less state call than the previous implementation and is safe as long as the clear logic properly works and is executed anytime a user changes their vault timeout action (lock or log out) & vault timeout (numeric value) * PM-5263 - VaultTimeoutSettingsSvc - Set token calls now updated to include vault timeout info. * PM-5263 - Update API Service - add state service and look up vault timeout details and pass to token service when setting token info. * PM-5263 - TokenService - update service dependencies. * PM-5263 - TokenService - Add new getAccessTokenByUserId method for state service use case. * PM-5263 - StateSvc - remove migrated methods and try to replace all usages of getAccessToken. WIP * PM-5263 - TokenSvc Migration - start on migrator * PM-5263 - (1) TokenSvc - Build new clearAccessTokenByUserId which is required by state service (2) TokenSvc - Update getToken to take an optional userId to handle another state service case (3) Add some documentation to TokenSvc abstraction. * PM-5263 - StateService - finish updating all calls within the state service which accessed token service state directly with calls to the new token service methods instead. * PM-5263 - TokenSvc Abstraction - Add more docs * PM-5263 - TokenSvc abstraction - more doc tweaks * PM-5263 - Web state service - add new token service dependency. * PM-5263 - User API Key Login Strategy - Update to pull vault timeout action and vault timeout from state service in order to pass to new token service endpoints for setting API key client id and secret. * PM-5263 - (1) Remove TokenSvc owned state from account (2) StateSvc - remove account scaffold logic for clearing removed account data. The same functionality will exist in the state provider framework via lifecycle hooks cleaning up this data and users getting initialized with null data by default. * PM-5263 - Add token service dependency to state service (WIP - desktop deps not working) * PM-5263 - Update services module on desktop and browser to add token svc dependency * PM-5263 - API service factory - add state service factory dependency that I missed initially to get browser building. * PM-5263 - TokenSvc - getToken/setToken/decodeToken --> getAccessToken/setAccessToken/decodeAccessToken * PM-5263 - TokenSvc State Provider Migrator - WIP - update expected acct type to match actual account * PM-5263 - TokenService - clearToken renamed to clearTokens * PM-5263 - CLI - NodeApiService - add state service dep to get CLI building. * PM-5263 - StateDefinitions - use unique state definition names * PM-5263 - StateSvc - remove getTimeoutBasedStorageOptions as no longer used. * PM-5263 - TokenSvc - Add TODO for figuring out how to store tokens in secure storage. * PM-5263 - StateSvc - remove get/set 2FA token - references migrated later. * PM-5263 - TODO: figure out if using same key definition names is an issue * PM-5263 - TokenServiceStateProviderMigrator written * PM-5263 - TokenServiceStateProviderMigrator - (1) Don't update legacy account if we only added a new state in state provider for 2FA token (2) Use for loop for easier debugging * PM-5263 - TokenServiceStateProviderMigrator test - WIP - migration testing mostly complete and passing. Rollback logic TODO. * PM-5263 - TokenServiceStateProviderMigrator - Add rollback logic to restore 2FA token from users to global. * PM-5263 - TokenServiceStateProviderMigrator - Refactor rollback to only set account once as not necessary to set it every time. * PM-5263 - TokenServiceStateProviderMigrator tests - test all rollback scenarios * PM-5263 - Remove TODO as don't need unique key def names as long as state def keys are unique. * PM-5263 - TokenSvc - update clearAccessTokenByUserId to use proper state provider helper method to set state. * PM-5263 - Revert accidentally committing settings.json changes. * PM-5263 - TokenSvc - update all 2FA token methods to require email so we can user specifically scope 2FA tokens while still storing them in global storage. * PM-5263 - Update all token service 2FA set / get / clear methods to pass in email. * PM-5263 - JslibServices module - add missed login service to login strategy svc deps. * PM-5263 - VaultTimeoutSettingsService - setVaultTimeoutOptions - rename token to accesToken for clarity. * PM-5263 - (1) TokenSvc - remove getAccessTokenByUserId and force consumers to use getAccessToken w/ optional user id to keep interface small (2) TokenSvc - attempt to implement secure storage on platforms that support it for access & refresh token storage (3) StateSvc - replace usage of getAccessTokenByUserId with getAccessToken * PM-5263 - TokenSvc - add platform utils and secure storage svc deps * PM-5263 - TODO: figure out what to do with broken migration * PM-5263 - TODO: update tests in light of latest 2FA token changes. * PM-5263 - TokenSvc - clean up TODO * PM-5263 - We should have tests for the token service. * PM-5263 - TokenSvc - setAccessToken - If platform supports secure storage and we are saving an access token, remove the access token from memory and disk to fully migrate to secure storage. * PM-5263 - TokenSvc - getAccessToken - Update logic to look at memory and disk first always and secure storage last to support the secure storage migration * PM-5263 - TokenSvc - setAccesToken - if user id null on a secure storage supporting platform, throw error. * PM-5263 - TokenService - (1) Refresh token now stored in secure storage (2) Refresh token set now private as we require a user id to store it in secure storage and we can use the setTokens method to enforce always setting the access token and refresh token together in order to extract a user id from the refresh token. (3) setTokens clientIdClientSecret param now optional * PM-5263 - TokenServiceStateProviderMigrator - update migration to take global but user scoped 2FA token storage changes into account. * PM-5263 - Remove old migration as it references state we are removing. Bump min version. Co-authored-by: Matt Gibson <git@mgibson.dev> * PM-5263 - TokenService - 2FA token methods now backed by global state record which maps email to individual tokens. * PM-5263 - WIP on Token Svc migrator and test updates based on new 2FA token storage changes. * PM-5263 - TokenSvc - (1) Add jira tickets to clean up state migration (2) Add state to track secure storage migration to improve # of reads to get data * PM-5263 - StateDef - consolidate name of token domain state defs per feedback from Justin + update migration tests * PM-5263 - TokenSvc - fix error message and add TODO * PM-5263 - Update token service migration + tests to pass after all 2FA token changes. * PM-5263 - Fix all login strategy tests which were failing due to token state provider changes + the addition of the loginService as a dependency in the base login strategy. * PM-5263 - Register TokenService state provider migration with migrator * PM-5263 - TokenSvc state migration - set tokens after initializing account * PM-5263 - TokenService changes - WIP - convert from ActiveUserStateProvider to just SingleUserStateProvider to avoid future circ dependency issues. Co-authored-by: Jake Fink <jlf0dev@users.noreply.github.com> * PM-5263 - TokenSvc - create getSecureStorageOptions for centralizing all logic for getting data out of SecureStorage. * PM-5263 - TokenSvc - (1) Refactor determineStorageLocation to also determine secure storage - created a TokenStorageLocation string enum to remove magic strings (2) Refactor setAccessToken to use switch (3) Refactor clearAccessTokenByUserId to clear all locations and not early return on secure storage b/c we only use secure storage if disk is the location but I don't want to require vault timeout data for this method. * PM-5263 - TokenSvc - getDataFromSecureStorage - Refactor to be more generic for easier re-use * PM-5263 - TokenSvc - Convert refresh token methods to use single user state and require user ids * PM-5263 - VaultTimeoutSettingsSvc - get user id and pass to access and refresh token methods. * PM-5263 - TokenSvc - refactor save secure storage logic into private helper. * PM-5263 - Base Login Strategy - per discussion with Justin, move save of tokens to before account initialization as we can always derive the user id from the access token. This will ensure that the account is initialized with the proper authN status. * PM-5263 - TokenSvc - latest refactor - update all methods to accept optional userId now as we can read active user id from global state provider without using activeUserStateProvider (thus, avoiding a circular dep and having to have every method accept in a mandatory user id). * PM-5263 - VaultTimeoutSettingsService - remove user id from token calls * PM-5263 - TokenSvc - update all places we instantiate token service to properly pass in new deps. * PM-5263 - TokenSvc migration is now 27th instead of 23rd. * PM-5263 - Browser - MainContextMenuHandler - Update service options to include PlatformUtilsServiceInitOptions as the TokenService requires that and the TokenService is now injected on the StateService * PM-5263 - TokenSvc migration test - update rollback tests to start with correct current version * PM-5263 - Create token service test file - WIP * PM-5263 - TokenSvc - tests WIP - instantiates working. * PM-5263 - TokenSvc - set2FAToken - use null coalesce to ensure record is instantiated for new users before setting data on it. * PM-5263 - TokenService tests - WIP - 2FA token tests. * PM-5263 - Worked with Justin to resolve desktop circular dependency issue by adding SUPPORTS_SECURE_STORAGE injection token instead of injecting PlatformUtilsService directly into TokenService. Co-authored-by: Justin Baur <19896123+justindbaur@users.noreply.github.com> * PM-5263 - TokenSvc tests - WIP - (1) Update TokenSvc instantiation to use new supportsSecureStorage (2) Test TwoFactorToken methods * PM-5263 - Fix SUPPORTS_SECURE_STORAGE injection token to properly call supportsSecureStorage message * PM-5263 - Token state testing * PM-5263 - TokenState fix name of describe * PM-5263 - TokenService - export TokenStorageLocation for use in tests. * PM-5263 - TokenSvc Tests WIP * PM-5263 - TokenSvc tests - access token logic mostly completed. * PM-5263 - TokenSvc Tests - more WIP - finish testing access token methods. * PM-5263 - TokenSvc WIP - another clear access token test. * PM-5263 - TokenSvc tests - WIP - SetTokens tested. * PM-5263 - Tweak test name * PM-5263 - TokenSvc tests - remove unnecessary describe around 2FA token methods. * PM-5263 - TokenSvc.clearAccessTokenByUserId renamed to just clearAccessToken * PM-5263 - TokenSvc - refactor clearTokens logic and implement individual clear logic which doesn't require vault timeout setting information. * PM-5263 - TokenSvc - Replace all places we have vaultTimeout: number with vaultTimeout: number | null to be accurate. * PM-5263 - TokenSvc.clearTokens - add check for user id; throw if not found * PM-5263 - TokenService - test clearTokens * PM-5263 - TokenSvc Tests - setRefreshToken tested * PM-5263 - TokenSvc tests - getRefreshToken tested + added a new getAccessToken test * PM-5263 - TokenSvc - ClearRefreshToken scenarios tested. * PM-5263 - TokenSvc.clearRefreshToken tests - fix copy pasta * PM-5263 - TokenSvc tests - (1) Fix mistakes in refresh token testing (2) Test setClientId for all scenarios * PM-5263 - TokenSvc tests - (1) Add some getClientId tests (2) clarify lack of awaits * PM-5263 - TokenSvc Tests - WIP - getClientId && clearClientId * PM-5263 - TokenService - getClientSecret - fix error message * PM-5263 - TokenService tests - test all client secret methods * PM-5263 - Update TokenSvc migration to 30th migration * PM-5263 - TokenService - update all tests to initialize data to undefined now that fake state provider supports faking data based on specific key definitions. * PM-5263 - (1) TokenSvc.decodeAccessToken - update static method's error handling (2) TokenSvc tests - test all decodeAccessToken scenarios * PM-5263 - TokenSvc - (1) Add DecodedAccessToken type (2) Refactor getTokenExpirationDate logic to use new type and make proper type checks for numbers for exp claim values. * PM-5263 - TokenSvc tests - test getTokenExpirationDate method. * PM-5263 - TokenSvc - (1) Update DecodedAccessToken docs (2) Tweak naming in tokenSecondsRemaining * PM-5263 - TokenSvc abstraction - add jsdoc for tokenSecondsRemaining * PM-5263 - TokenSvc tests - test tokenSecondsRemaining * PM-5263 - TokenSvc - DecodedAccessToken type - update sstamp info * PM-5263 - TokenService - fix flaky tokenSecondsRemaining tests by locking time * PM-5263 - TokenSvc Tests - Test tokenNeedsRefresh * PM-5263 - (1) TokenSvc - Refactor getUserId to add extra safety (2) TokenSvc tests - test getUserId * PM-5263 - (1) TokenSvc - refactor getUserIdFromAccessToken to handle decoding errors (2) TokenSvc tests - test getUserIdFromAccessToken * PM-5263 - (1) TokenSvc - Refactor getEmail to handle decoding errors + check for specific, expected type (2) TokenSvc tests - test getEmail * PM-5263 - TokenSvc tests - clean up comment * PM-5263 - (1) TokenSvc - getEmailVerified - refactor (2) TokenSvc tests - add getEmailVerified tests * PM-5263 - (1) TokenSvc - refactor getName (2) TokenSvc tests - test getName * PM-5263 - (1) TokenSvc - refactor getIssuer (2) TokenSvc tests - test getIssuer * PM-5263 - TokenSvc - remove unnecessary "as type" statements now that we have a decoded access token type * PM-5263 - (1) TokenSvc - refactor getIsExternal (2) TokenSvc Tests - test getIsExternal * PM-5263 - TokenSvc abstraction - tune up rest of docs. * PM-5263 - TokenSvc - clean up promise<any> and replace with promise<void> * PM-5263 - TokenSvc abstraction - more docs. * PM-5263 - Clean up TODO as I've tested every method in token svc. * PM-5263 - (1) Extract JWT decode logic into auth owned utility function out of the token service (2) Update TokenService decode logic to use new utility function (3) Update LastPassDirectImportService + vault.ts to use new utility function and remove token service dependency. (4) Update tests + migrate tests to new utility test file. * PM-5263 - Rename decodeJwtTokenToJson to decode-jwt-token-to-json to meet lint rules excluding capitals * PM-5263 - TokenSvc + tests - fix all get methods to return undefined like they did before instead of throwing an error if a user id isn't provided. * PM-5263 - Services.module - add missing token service dep * PM-5263 - Update token svc migrations to be 32nd migration * PM-5263 - Popup - Services.module - Remove token service as it no longer requires a background service due to the migration to state provider. The service definition in jslib-services module is enough. * PM-5263 - BaseLoginStrategy - Extract email out of getTwoFactorToken method call for easier debugging. * PM-5263 - Login Comp - Set email into memory on login service so that base login strategy can access user email for looking up 2FA token stored in global state. * PM-5263 - (1) LoginComp - remove loginSvc.setEmail call as no longer necessary + introduced issues w/ popup and background in browser extension (2) AuthReq & Password login strategies now just pass in email to buildTwoFactor method. * PM-5263 - SsoLoginSvc + abstraction - Add key definition and get/set methods for saving user email in session storage so it persists across the SSO redirect. * PM-5263 - Base Login Strategy - BuildTwoFactor - only try to get 2FA token if we have an email to look up their token * PM-5263 - Remove LoginService dependency from LoginStrategyService * PM-5263 - (1) Save off user email when they click enterprise SSO on all clients in login comp (2) Retrieve it and pass it into login strategy in SSO comp * PM-5263 - (1) TokenSvc - update 2FA token methods to be more safe in case user removes record from local storage (2) Add test cases + missing clearTwoFactorToken tests * PM-5263 - Browser SSO login - save user email for browser SSO process * PM-5263 - Finish removing login service from login strategy tests. * PM-5263 - More removals of the login service from the login strategy tests. * PM-5263 - Main.ts - platformUtilsSvc no longer used in TokenSvc so remove it from desktop main.ts * PM-5263 - Fix failing login strategy service tests * PM-5263 - Bump token svc migration values to migration 35 after merging in main * PM-5263 - Bump token svc migration version * PM-5263 - TokenService.clearTwoFactorToken - use delete instead of setting values to null per discussion with Justin Co-authored-by: Justin Baur <19896123+justindbaur@users.noreply.github.com> * PM-5263 - TokenSvc + decode JWT token tests - anonymize my information Co-authored-by: Justin Baur <19896123+justindbaur@users.noreply.github.com> * PM-5263 - TokenSvc tests - update clear token tests based on actual deletion * PM-5263 - Add docs per PR feedback * PM-5263 - (1) Move ownership of clearing two factor token on rejection from server to base login strategy (2) Each login strategy that supports remember 2FA logic now persists user entered email in its data (3) Base login strategy processTwoFactorResponse now clears 2FA token (4) Updated base login strategy tests to affirm the clearing of the 2FA token * Update libs/auth/src/common/login-strategies/login.strategy.ts Co-authored-by: Jake Fink <jfink@bitwarden.com> * Update libs/auth/src/common/login-strategies/password-login.strategy.ts Co-authored-by: Jake Fink <jfink@bitwarden.com> * PM-5263 - Login Strategy - per PR feedback, add jsdoc comments to each method I've touched for this PR. * PM-5263 - (1) TokenSvc - adjust setTokens, setAccessToken, setRefreshToken, and clearRefreshToken based on PR feedback to remove optional user ids where possible and improve public interface (2) TokenSvc Abstraction - update docs and abstractions based on removed user ids and changed logic (3) TokenSvc tests - update tests to add new test cases, remove no longer relevant ones, and update test names. * PM-5263 - Bump migrations again --------- Co-authored-by: Matt Gibson <git@mgibson.dev> Co-authored-by: Jake Fink <jlf0dev@users.noreply.github.com> Co-authored-by: Justin Baur <19896123+justindbaur@users.noreply.github.com> Co-authored-by: Jake Fink <jfink@bitwarden.com>
1952 lines
66 KiB
TypeScript
1952 lines
66 KiB
TypeScript
import { ApiService as ApiServiceAbstraction } from "../abstractions/api.service";
|
|
import { OrganizationConnectionType } from "../admin-console/enums";
|
|
import { OrganizationSponsorshipCreateRequest } from "../admin-console/models/request/organization/organization-sponsorship-create.request";
|
|
import { OrganizationSponsorshipRedeemRequest } from "../admin-console/models/request/organization/organization-sponsorship-redeem.request";
|
|
import { OrganizationConnectionRequest } from "../admin-console/models/request/organization-connection.request";
|
|
import { ProviderAddOrganizationRequest } from "../admin-console/models/request/provider/provider-add-organization.request";
|
|
import { ProviderOrganizationCreateRequest } from "../admin-console/models/request/provider/provider-organization-create.request";
|
|
import { ProviderSetupRequest } from "../admin-console/models/request/provider/provider-setup.request";
|
|
import { ProviderUpdateRequest } from "../admin-console/models/request/provider/provider-update.request";
|
|
import { ProviderUserAcceptRequest } from "../admin-console/models/request/provider/provider-user-accept.request";
|
|
import { ProviderUserBulkConfirmRequest } from "../admin-console/models/request/provider/provider-user-bulk-confirm.request";
|
|
import { ProviderUserBulkRequest } from "../admin-console/models/request/provider/provider-user-bulk.request";
|
|
import { ProviderUserConfirmRequest } from "../admin-console/models/request/provider/provider-user-confirm.request";
|
|
import { ProviderUserInviteRequest } from "../admin-console/models/request/provider/provider-user-invite.request";
|
|
import { ProviderUserUpdateRequest } from "../admin-console/models/request/provider/provider-user-update.request";
|
|
import { SelectionReadOnlyRequest } from "../admin-console/models/request/selection-read-only.request";
|
|
import {
|
|
OrganizationConnectionConfigApis,
|
|
OrganizationConnectionResponse,
|
|
} from "../admin-console/models/response/organization-connection.response";
|
|
import { OrganizationExportResponse } from "../admin-console/models/response/organization-export.response";
|
|
import { OrganizationSponsorshipSyncStatusResponse } from "../admin-console/models/response/organization-sponsorship-sync-status.response";
|
|
import {
|
|
ProviderOrganizationOrganizationDetailsResponse,
|
|
ProviderOrganizationResponse,
|
|
} from "../admin-console/models/response/provider/provider-organization.response";
|
|
import { ProviderUserBulkPublicKeyResponse } from "../admin-console/models/response/provider/provider-user-bulk-public-key.response";
|
|
import { ProviderUserBulkResponse } from "../admin-console/models/response/provider/provider-user-bulk.response";
|
|
import {
|
|
ProviderUserResponse,
|
|
ProviderUserUserDetailsResponse,
|
|
} from "../admin-console/models/response/provider/provider-user.response";
|
|
import { ProviderResponse } from "../admin-console/models/response/provider/provider.response";
|
|
import { SelectionReadOnlyResponse } from "../admin-console/models/response/selection-read-only.response";
|
|
import { TokenService } from "../auth/abstractions/token.service";
|
|
import { CreateAuthRequest } from "../auth/models/request/create-auth.request";
|
|
import { DeviceVerificationRequest } from "../auth/models/request/device-verification.request";
|
|
import { EmailTokenRequest } from "../auth/models/request/email-token.request";
|
|
import { EmailRequest } from "../auth/models/request/email.request";
|
|
import { DeviceRequest } from "../auth/models/request/identity-token/device.request";
|
|
import { PasswordTokenRequest } from "../auth/models/request/identity-token/password-token.request";
|
|
import { SsoTokenRequest } from "../auth/models/request/identity-token/sso-token.request";
|
|
import { TokenTwoFactorRequest } from "../auth/models/request/identity-token/token-two-factor.request";
|
|
import { UserApiTokenRequest } from "../auth/models/request/identity-token/user-api-token.request";
|
|
import { WebAuthnLoginTokenRequest } from "../auth/models/request/identity-token/webauthn-login-token.request";
|
|
import { KeyConnectorUserKeyRequest } from "../auth/models/request/key-connector-user-key.request";
|
|
import { PasswordHintRequest } from "../auth/models/request/password-hint.request";
|
|
import { PasswordRequest } from "../auth/models/request/password.request";
|
|
import { PasswordlessAuthRequest } from "../auth/models/request/passwordless-auth.request";
|
|
import { SecretVerificationRequest } from "../auth/models/request/secret-verification.request";
|
|
import { SetKeyConnectorKeyRequest } from "../auth/models/request/set-key-connector-key.request";
|
|
import { SetPasswordRequest } from "../auth/models/request/set-password.request";
|
|
import { TwoFactorEmailRequest } from "../auth/models/request/two-factor-email.request";
|
|
import { TwoFactorProviderRequest } from "../auth/models/request/two-factor-provider.request";
|
|
import { TwoFactorRecoveryRequest } from "../auth/models/request/two-factor-recovery.request";
|
|
import { UpdateProfileRequest } from "../auth/models/request/update-profile.request";
|
|
import { UpdateTempPasswordRequest } from "../auth/models/request/update-temp-password.request";
|
|
import { UpdateTwoFactorAuthenticatorRequest } from "../auth/models/request/update-two-factor-authenticator.request";
|
|
import { UpdateTwoFactorDuoRequest } from "../auth/models/request/update-two-factor-duo.request";
|
|
import { UpdateTwoFactorEmailRequest } from "../auth/models/request/update-two-factor-email.request";
|
|
import { UpdateTwoFactorWebAuthnDeleteRequest } from "../auth/models/request/update-two-factor-web-authn-delete.request";
|
|
import { UpdateTwoFactorWebAuthnRequest } from "../auth/models/request/update-two-factor-web-authn.request";
|
|
import { UpdateTwoFactorYubioOtpRequest } from "../auth/models/request/update-two-factor-yubio-otp.request";
|
|
import { ApiKeyResponse } from "../auth/models/response/api-key.response";
|
|
import { AuthRequestResponse } from "../auth/models/response/auth-request.response";
|
|
import { DeviceVerificationResponse } from "../auth/models/response/device-verification.response";
|
|
import { IdentityCaptchaResponse } from "../auth/models/response/identity-captcha.response";
|
|
import { IdentityTokenResponse } from "../auth/models/response/identity-token.response";
|
|
import { IdentityTwoFactorResponse } from "../auth/models/response/identity-two-factor.response";
|
|
import { KeyConnectorUserKeyResponse } from "../auth/models/response/key-connector-user-key.response";
|
|
import { MasterPasswordPolicyResponse } from "../auth/models/response/master-password-policy.response";
|
|
import { PreloginResponse } from "../auth/models/response/prelogin.response";
|
|
import { RegisterResponse } from "../auth/models/response/register.response";
|
|
import { SsoPreValidateResponse } from "../auth/models/response/sso-pre-validate.response";
|
|
import { TwoFactorAuthenticatorResponse } from "../auth/models/response/two-factor-authenticator.response";
|
|
import { TwoFactorDuoResponse } from "../auth/models/response/two-factor-duo.response";
|
|
import { TwoFactorEmailResponse } from "../auth/models/response/two-factor-email.response";
|
|
import { TwoFactorProviderResponse } from "../auth/models/response/two-factor-provider.response";
|
|
import { TwoFactorRecoverResponse } from "../auth/models/response/two-factor-recover.response";
|
|
import {
|
|
ChallengeResponse,
|
|
TwoFactorWebAuthnResponse,
|
|
} from "../auth/models/response/two-factor-web-authn.response";
|
|
import { TwoFactorYubiKeyResponse } from "../auth/models/response/two-factor-yubi-key.response";
|
|
import { BitPayInvoiceRequest } from "../billing/models/request/bit-pay-invoice.request";
|
|
import { PaymentRequest } from "../billing/models/request/payment.request";
|
|
import { TaxInfoUpdateRequest } from "../billing/models/request/tax-info-update.request";
|
|
import { BillingHistoryResponse } from "../billing/models/response/billing-history.response";
|
|
import { BillingPaymentResponse } from "../billing/models/response/billing-payment.response";
|
|
import { PaymentResponse } from "../billing/models/response/payment.response";
|
|
import { PlanResponse } from "../billing/models/response/plan.response";
|
|
import { SubscriptionResponse } from "../billing/models/response/subscription.response";
|
|
import { TaxInfoResponse } from "../billing/models/response/tax-info.response";
|
|
import { TaxRateResponse } from "../billing/models/response/tax-rate.response";
|
|
import { DeviceType } from "../enums";
|
|
import { VaultTimeoutAction } from "../enums/vault-timeout-action.enum";
|
|
import { CollectionBulkDeleteRequest } from "../models/request/collection-bulk-delete.request";
|
|
import { DeleteRecoverRequest } from "../models/request/delete-recover.request";
|
|
import { EventRequest } from "../models/request/event.request";
|
|
import { KdfRequest } from "../models/request/kdf.request";
|
|
import { KeysRequest } from "../models/request/keys.request";
|
|
import { OrganizationImportRequest } from "../models/request/organization-import.request";
|
|
import { PreloginRequest } from "../models/request/prelogin.request";
|
|
import { RegisterRequest } from "../models/request/register.request";
|
|
import { StorageRequest } from "../models/request/storage.request";
|
|
import { UpdateAvatarRequest } from "../models/request/update-avatar.request";
|
|
import { UpdateDomainsRequest } from "../models/request/update-domains.request";
|
|
import { VerifyDeleteRecoverRequest } from "../models/request/verify-delete-recover.request";
|
|
import { VerifyEmailRequest } from "../models/request/verify-email.request";
|
|
import { BreachAccountResponse } from "../models/response/breach-account.response";
|
|
import { DomainsResponse } from "../models/response/domains.response";
|
|
import { ErrorResponse } from "../models/response/error.response";
|
|
import { EventResponse } from "../models/response/event.response";
|
|
import { ListResponse } from "../models/response/list.response";
|
|
import { ProfileResponse } from "../models/response/profile.response";
|
|
import { UserKeyResponse } from "../models/response/user-key.response";
|
|
import { AppIdService } from "../platform/abstractions/app-id.service";
|
|
import { EnvironmentService } from "../platform/abstractions/environment.service";
|
|
import { PlatformUtilsService } from "../platform/abstractions/platform-utils.service";
|
|
import { StateService } from "../platform/abstractions/state.service";
|
|
import { Utils } from "../platform/misc/utils";
|
|
import { AttachmentRequest } from "../vault/models/request/attachment.request";
|
|
import { CipherBulkDeleteRequest } from "../vault/models/request/cipher-bulk-delete.request";
|
|
import { CipherBulkMoveRequest } from "../vault/models/request/cipher-bulk-move.request";
|
|
import { CipherBulkRestoreRequest } from "../vault/models/request/cipher-bulk-restore.request";
|
|
import { CipherBulkShareRequest } from "../vault/models/request/cipher-bulk-share.request";
|
|
import { CipherCollectionsRequest } from "../vault/models/request/cipher-collections.request";
|
|
import { CipherCreateRequest } from "../vault/models/request/cipher-create.request";
|
|
import { CipherPartialRequest } from "../vault/models/request/cipher-partial.request";
|
|
import { CipherShareRequest } from "../vault/models/request/cipher-share.request";
|
|
import { CipherRequest } from "../vault/models/request/cipher.request";
|
|
import { CollectionRequest } from "../vault/models/request/collection.request";
|
|
import { AttachmentUploadDataResponse } from "../vault/models/response/attachment-upload-data.response";
|
|
import { AttachmentResponse } from "../vault/models/response/attachment.response";
|
|
import { CipherResponse } from "../vault/models/response/cipher.response";
|
|
import {
|
|
CollectionAccessDetailsResponse,
|
|
CollectionDetailsResponse,
|
|
CollectionResponse,
|
|
} from "../vault/models/response/collection.response";
|
|
import { SyncResponse } from "../vault/models/response/sync.response";
|
|
|
|
/**
|
|
* @deprecated The `ApiService` class is deprecated and calls should be extracted into individual
|
|
* api services. The `send` method is still allowed to be used within api services. For background
|
|
* of this decision please read https://contributing.bitwarden.com/architecture/adr/refactor-api-service.
|
|
*/
|
|
export class ApiService implements ApiServiceAbstraction {
|
|
private device: DeviceType;
|
|
private deviceType: string;
|
|
private isWebClient = false;
|
|
private isDesktopClient = false;
|
|
|
|
constructor(
|
|
private tokenService: TokenService,
|
|
private platformUtilsService: PlatformUtilsService,
|
|
private environmentService: EnvironmentService,
|
|
private appIdService: AppIdService,
|
|
private stateService: StateService,
|
|
private logoutCallback: (expired: boolean) => Promise<void>,
|
|
private customUserAgent: string = null,
|
|
) {
|
|
this.device = platformUtilsService.getDevice();
|
|
this.deviceType = this.device.toString();
|
|
this.isWebClient =
|
|
this.device === DeviceType.IEBrowser ||
|
|
this.device === DeviceType.ChromeBrowser ||
|
|
this.device === DeviceType.EdgeBrowser ||
|
|
this.device === DeviceType.FirefoxBrowser ||
|
|
this.device === DeviceType.OperaBrowser ||
|
|
this.device === DeviceType.SafariBrowser ||
|
|
this.device === DeviceType.UnknownBrowser ||
|
|
this.device === DeviceType.VivaldiBrowser;
|
|
this.isDesktopClient =
|
|
this.device === DeviceType.WindowsDesktop ||
|
|
this.device === DeviceType.MacOsDesktop ||
|
|
this.device === DeviceType.LinuxDesktop ||
|
|
this.device === DeviceType.WindowsCLI ||
|
|
this.device === DeviceType.MacOsCLI ||
|
|
this.device === DeviceType.LinuxCLI;
|
|
}
|
|
|
|
// Auth APIs
|
|
|
|
async postIdentityToken(
|
|
request:
|
|
| UserApiTokenRequest
|
|
| PasswordTokenRequest
|
|
| SsoTokenRequest
|
|
| WebAuthnLoginTokenRequest,
|
|
): Promise<IdentityTokenResponse | IdentityTwoFactorResponse | IdentityCaptchaResponse> {
|
|
const headers = new Headers({
|
|
"Content-Type": "application/x-www-form-urlencoded; charset=utf-8",
|
|
Accept: "application/json",
|
|
"Device-Type": this.deviceType,
|
|
});
|
|
if (this.customUserAgent != null) {
|
|
headers.set("User-Agent", this.customUserAgent);
|
|
}
|
|
request.alterIdentityTokenHeaders(headers);
|
|
|
|
const identityToken =
|
|
request instanceof UserApiTokenRequest
|
|
? request.toIdentityToken()
|
|
: request.toIdentityToken(this.platformUtilsService.getClientType());
|
|
|
|
const response = await this.fetch(
|
|
new Request(this.environmentService.getIdentityUrl() + "/connect/token", {
|
|
body: this.qsStringify(identityToken),
|
|
credentials: this.getCredentials(),
|
|
cache: "no-store",
|
|
headers: headers,
|
|
method: "POST",
|
|
}),
|
|
);
|
|
|
|
let responseJson: any = null;
|
|
if (this.isJsonResponse(response)) {
|
|
responseJson = await response.json();
|
|
}
|
|
|
|
if (responseJson != null) {
|
|
if (response.status === 200) {
|
|
return new IdentityTokenResponse(responseJson);
|
|
} else if (
|
|
response.status === 400 &&
|
|
responseJson.TwoFactorProviders2 &&
|
|
Object.keys(responseJson.TwoFactorProviders2).length
|
|
) {
|
|
return new IdentityTwoFactorResponse(responseJson);
|
|
} else if (
|
|
response.status === 400 &&
|
|
responseJson.HCaptcha_SiteKey &&
|
|
Object.keys(responseJson.HCaptcha_SiteKey).length
|
|
) {
|
|
return new IdentityCaptchaResponse(responseJson);
|
|
}
|
|
}
|
|
|
|
return Promise.reject(new ErrorResponse(responseJson, response.status, true));
|
|
}
|
|
|
|
async refreshIdentityToken(): Promise<any> {
|
|
try {
|
|
await this.doAuthRefresh();
|
|
} catch (e) {
|
|
return Promise.reject(null);
|
|
}
|
|
}
|
|
|
|
// TODO: PM-3519: Create and move to AuthRequest Api service
|
|
async postAuthRequest(request: CreateAuthRequest): Promise<AuthRequestResponse> {
|
|
const r = await this.send("POST", "/auth-requests/", request, false, true);
|
|
return new AuthRequestResponse(r);
|
|
}
|
|
async postAdminAuthRequest(request: CreateAuthRequest): Promise<AuthRequestResponse> {
|
|
const r = await this.send("POST", "/auth-requests/admin-request", request, true, true);
|
|
return new AuthRequestResponse(r);
|
|
}
|
|
|
|
async getAuthResponse(id: string, accessCode: string): Promise<AuthRequestResponse> {
|
|
const path = `/auth-requests/${id}/response?code=${accessCode}`;
|
|
const r = await this.send("GET", path, null, false, true);
|
|
return new AuthRequestResponse(r);
|
|
}
|
|
|
|
async getAuthRequest(id: string): Promise<AuthRequestResponse> {
|
|
const path = `/auth-requests/${id}`;
|
|
const r = await this.send("GET", path, null, true, true);
|
|
return new AuthRequestResponse(r);
|
|
}
|
|
|
|
async putAuthRequest(id: string, request: PasswordlessAuthRequest): Promise<AuthRequestResponse> {
|
|
const path = `/auth-requests/${id}`;
|
|
const r = await this.send("PUT", path, request, true, true);
|
|
return new AuthRequestResponse(r);
|
|
}
|
|
|
|
async getAuthRequests(): Promise<ListResponse<AuthRequestResponse>> {
|
|
const path = `/auth-requests/`;
|
|
const r = await this.send("GET", path, null, true, true);
|
|
return new ListResponse(r, AuthRequestResponse);
|
|
}
|
|
|
|
async getLastAuthRequest(): Promise<AuthRequestResponse> {
|
|
const requests = await this.getAuthRequests();
|
|
const activeRequests = requests.data.filter((m) => !m.isAnswered && !m.isExpired);
|
|
const lastRequest = activeRequests.sort((a: AuthRequestResponse, b: AuthRequestResponse) =>
|
|
a.creationDate.localeCompare(b.creationDate),
|
|
)[activeRequests.length - 1];
|
|
return lastRequest;
|
|
}
|
|
|
|
// Account APIs
|
|
|
|
async getProfile(): Promise<ProfileResponse> {
|
|
const r = await this.send("GET", "/accounts/profile", null, true, true);
|
|
return new ProfileResponse(r);
|
|
}
|
|
|
|
async getUserSubscription(): Promise<SubscriptionResponse> {
|
|
const r = await this.send("GET", "/accounts/subscription", null, true, true);
|
|
return new SubscriptionResponse(r);
|
|
}
|
|
|
|
async getTaxInfo(): Promise<TaxInfoResponse> {
|
|
const r = await this.send("GET", "/accounts/tax", null, true, true);
|
|
return new TaxInfoResponse(r);
|
|
}
|
|
|
|
async putProfile(request: UpdateProfileRequest): Promise<ProfileResponse> {
|
|
const r = await this.send("PUT", "/accounts/profile", request, true, true);
|
|
return new ProfileResponse(r);
|
|
}
|
|
|
|
async putAvatar(request: UpdateAvatarRequest): Promise<ProfileResponse> {
|
|
const r = await this.send("PUT", "/accounts/avatar", request, true, true);
|
|
return new ProfileResponse(r);
|
|
}
|
|
|
|
putTaxInfo(request: TaxInfoUpdateRequest): Promise<any> {
|
|
return this.send("PUT", "/accounts/tax", request, true, false);
|
|
}
|
|
|
|
async postPrelogin(request: PreloginRequest): Promise<PreloginResponse> {
|
|
const r = await this.send(
|
|
"POST",
|
|
"/accounts/prelogin",
|
|
request,
|
|
false,
|
|
true,
|
|
this.environmentService.getIdentityUrl(),
|
|
);
|
|
return new PreloginResponse(r);
|
|
}
|
|
|
|
postEmailToken(request: EmailTokenRequest): Promise<any> {
|
|
return this.send("POST", "/accounts/email-token", request, true, false);
|
|
}
|
|
|
|
postEmail(request: EmailRequest): Promise<any> {
|
|
return this.send("POST", "/accounts/email", request, true, false);
|
|
}
|
|
|
|
postPassword(request: PasswordRequest): Promise<any> {
|
|
return this.send("POST", "/accounts/password", request, true, false);
|
|
}
|
|
|
|
setPassword(request: SetPasswordRequest): Promise<any> {
|
|
return this.send("POST", "/accounts/set-password", request, true, false);
|
|
}
|
|
|
|
postSetKeyConnectorKey(request: SetKeyConnectorKeyRequest): Promise<any> {
|
|
return this.send("POST", "/accounts/set-key-connector-key", request, true, false);
|
|
}
|
|
|
|
postSecurityStamp(request: SecretVerificationRequest): Promise<any> {
|
|
return this.send("POST", "/accounts/security-stamp", request, true, false);
|
|
}
|
|
|
|
async getAccountRevisionDate(): Promise<number> {
|
|
const r = await this.send("GET", "/accounts/revision-date", null, true, true);
|
|
return r as number;
|
|
}
|
|
|
|
postPasswordHint(request: PasswordHintRequest): Promise<any> {
|
|
return this.send("POST", "/accounts/password-hint", request, false, false);
|
|
}
|
|
|
|
async postRegister(request: RegisterRequest): Promise<RegisterResponse> {
|
|
const r = await this.send(
|
|
"POST",
|
|
"/accounts/register",
|
|
request,
|
|
false,
|
|
true,
|
|
this.environmentService.getIdentityUrl(),
|
|
);
|
|
return new RegisterResponse(r);
|
|
}
|
|
|
|
async postPremium(data: FormData): Promise<PaymentResponse> {
|
|
const r = await this.send("POST", "/accounts/premium", data, true, true);
|
|
return new PaymentResponse(r);
|
|
}
|
|
|
|
postReinstatePremium(): Promise<any> {
|
|
return this.send("POST", "/accounts/reinstate-premium", null, true, false);
|
|
}
|
|
|
|
postCancelPremium(): Promise<any> {
|
|
return this.send("POST", "/accounts/cancel-premium", null, true, false);
|
|
}
|
|
|
|
async postAccountStorage(request: StorageRequest): Promise<PaymentResponse> {
|
|
const r = await this.send("POST", "/accounts/storage", request, true, true);
|
|
return new PaymentResponse(r);
|
|
}
|
|
|
|
postAccountPayment(request: PaymentRequest): Promise<void> {
|
|
return this.send("POST", "/accounts/payment", request, true, false);
|
|
}
|
|
|
|
postAccountLicense(data: FormData): Promise<any> {
|
|
return this.send("POST", "/accounts/license", data, true, false);
|
|
}
|
|
|
|
postAccountKeys(request: KeysRequest): Promise<any> {
|
|
return this.send("POST", "/accounts/keys", request, true, false);
|
|
}
|
|
|
|
postAccountVerifyEmail(): Promise<any> {
|
|
return this.send("POST", "/accounts/verify-email", null, true, false);
|
|
}
|
|
|
|
postAccountVerifyEmailToken(request: VerifyEmailRequest): Promise<any> {
|
|
return this.send("POST", "/accounts/verify-email-token", request, false, false);
|
|
}
|
|
|
|
postAccountVerifyPassword(
|
|
request: SecretVerificationRequest,
|
|
): Promise<MasterPasswordPolicyResponse> {
|
|
return this.send("POST", "/accounts/verify-password", request, true, true);
|
|
}
|
|
|
|
postAccountRecoverDelete(request: DeleteRecoverRequest): Promise<any> {
|
|
return this.send("POST", "/accounts/delete-recover", request, false, false);
|
|
}
|
|
|
|
postAccountRecoverDeleteToken(request: VerifyDeleteRecoverRequest): Promise<any> {
|
|
return this.send("POST", "/accounts/delete-recover-token", request, false, false);
|
|
}
|
|
|
|
postAccountKdf(request: KdfRequest): Promise<any> {
|
|
return this.send("POST", "/accounts/kdf", request, true, false);
|
|
}
|
|
|
|
async deleteSsoUser(organizationId: string): Promise<void> {
|
|
return this.send("DELETE", "/accounts/sso/" + organizationId, null, true, false);
|
|
}
|
|
|
|
async getSsoUserIdentifier(): Promise<string> {
|
|
return this.send("GET", "/accounts/sso/user-identifier", null, true, true);
|
|
}
|
|
|
|
async postUserApiKey(id: string, request: SecretVerificationRequest): Promise<ApiKeyResponse> {
|
|
const r = await this.send("POST", "/accounts/api-key", request, true, true);
|
|
return new ApiKeyResponse(r);
|
|
}
|
|
|
|
async postUserRotateApiKey(
|
|
id: string,
|
|
request: SecretVerificationRequest,
|
|
): Promise<ApiKeyResponse> {
|
|
const r = await this.send("POST", "/accounts/rotate-api-key", request, true, true);
|
|
return new ApiKeyResponse(r);
|
|
}
|
|
|
|
putUpdateTempPassword(request: UpdateTempPasswordRequest): Promise<any> {
|
|
return this.send("PUT", "/accounts/update-temp-password", request, true, false);
|
|
}
|
|
|
|
postConvertToKeyConnector(): Promise<void> {
|
|
return this.send("POST", "/accounts/convert-to-key-connector", null, true, false);
|
|
}
|
|
|
|
// Account Billing APIs
|
|
|
|
async getUserBillingHistory(): Promise<BillingHistoryResponse> {
|
|
const r = await this.send("GET", "/accounts/billing/history", null, true, true);
|
|
return new BillingHistoryResponse(r);
|
|
}
|
|
|
|
async getUserBillingPayment(): Promise<BillingPaymentResponse> {
|
|
const r = await this.send("GET", "/accounts/billing/payment-method", null, true, true);
|
|
return new BillingPaymentResponse(r);
|
|
}
|
|
|
|
// Cipher APIs
|
|
|
|
async getCipher(id: string): Promise<CipherResponse> {
|
|
const r = await this.send("GET", "/ciphers/" + id, null, true, true);
|
|
return new CipherResponse(r);
|
|
}
|
|
|
|
async getFullCipherDetails(id: string): Promise<CipherResponse> {
|
|
const r = await this.send("GET", "/ciphers/" + id + "/details", null, true, true);
|
|
return new CipherResponse(r);
|
|
}
|
|
|
|
async getCipherAdmin(id: string): Promise<CipherResponse> {
|
|
const r = await this.send("GET", "/ciphers/" + id + "/admin", null, true, true);
|
|
return new CipherResponse(r);
|
|
}
|
|
|
|
async getCiphersOrganization(organizationId: string): Promise<ListResponse<CipherResponse>> {
|
|
const r = await this.send(
|
|
"GET",
|
|
"/ciphers/organization-details?organizationId=" + organizationId,
|
|
null,
|
|
true,
|
|
true,
|
|
);
|
|
return new ListResponse(r, CipherResponse);
|
|
}
|
|
|
|
async postCipher(request: CipherRequest): Promise<CipherResponse> {
|
|
const r = await this.send("POST", "/ciphers", request, true, true);
|
|
return new CipherResponse(r);
|
|
}
|
|
|
|
async postCipherCreate(request: CipherCreateRequest): Promise<CipherResponse> {
|
|
const r = await this.send("POST", "/ciphers/create", request, true, true);
|
|
return new CipherResponse(r);
|
|
}
|
|
|
|
async postCipherAdmin(request: CipherCreateRequest): Promise<CipherResponse> {
|
|
const r = await this.send("POST", "/ciphers/admin", request, true, true);
|
|
return new CipherResponse(r);
|
|
}
|
|
|
|
async putCipher(id: string, request: CipherRequest): Promise<CipherResponse> {
|
|
const r = await this.send("PUT", "/ciphers/" + id, request, true, true);
|
|
return new CipherResponse(r);
|
|
}
|
|
|
|
async putPartialCipher(id: string, request: CipherPartialRequest): Promise<CipherResponse> {
|
|
const r = await this.send("PUT", "/ciphers/" + id + "/partial", request, true, true);
|
|
return new CipherResponse(r);
|
|
}
|
|
|
|
async putCipherAdmin(id: string, request: CipherRequest): Promise<CipherResponse> {
|
|
const r = await this.send("PUT", "/ciphers/" + id + "/admin", request, true, true);
|
|
return new CipherResponse(r);
|
|
}
|
|
|
|
deleteCipher(id: string): Promise<any> {
|
|
return this.send("DELETE", "/ciphers/" + id, null, true, false);
|
|
}
|
|
|
|
deleteCipherAdmin(id: string): Promise<any> {
|
|
return this.send("DELETE", "/ciphers/" + id + "/admin", null, true, false);
|
|
}
|
|
|
|
deleteManyCiphers(request: CipherBulkDeleteRequest): Promise<any> {
|
|
return this.send("DELETE", "/ciphers", request, true, false);
|
|
}
|
|
|
|
deleteManyCiphersAdmin(request: CipherBulkDeleteRequest): Promise<any> {
|
|
return this.send("DELETE", "/ciphers/admin", request, true, false);
|
|
}
|
|
|
|
putMoveCiphers(request: CipherBulkMoveRequest): Promise<any> {
|
|
return this.send("PUT", "/ciphers/move", request, true, false);
|
|
}
|
|
|
|
async putShareCipher(id: string, request: CipherShareRequest): Promise<CipherResponse> {
|
|
const r = await this.send("PUT", "/ciphers/" + id + "/share", request, true, true);
|
|
return new CipherResponse(r);
|
|
}
|
|
|
|
putShareCiphers(request: CipherBulkShareRequest): Promise<any> {
|
|
return this.send("PUT", "/ciphers/share", request, true, false);
|
|
}
|
|
|
|
putCipherCollections(id: string, request: CipherCollectionsRequest): Promise<any> {
|
|
return this.send("PUT", "/ciphers/" + id + "/collections", request, true, false);
|
|
}
|
|
|
|
putCipherCollectionsAdmin(id: string, request: CipherCollectionsRequest): Promise<any> {
|
|
return this.send("PUT", "/ciphers/" + id + "/collections-admin", request, true, false);
|
|
}
|
|
|
|
postPurgeCiphers(
|
|
request: SecretVerificationRequest,
|
|
organizationId: string = null,
|
|
): Promise<any> {
|
|
let path = "/ciphers/purge";
|
|
if (organizationId != null) {
|
|
path += "?organizationId=" + organizationId;
|
|
}
|
|
return this.send("POST", path, request, true, false);
|
|
}
|
|
|
|
putDeleteCipher(id: string): Promise<any> {
|
|
return this.send("PUT", "/ciphers/" + id + "/delete", null, true, false);
|
|
}
|
|
|
|
putDeleteCipherAdmin(id: string): Promise<any> {
|
|
return this.send("PUT", "/ciphers/" + id + "/delete-admin", null, true, false);
|
|
}
|
|
|
|
putDeleteManyCiphers(request: CipherBulkDeleteRequest): Promise<any> {
|
|
return this.send("PUT", "/ciphers/delete", request, true, false);
|
|
}
|
|
|
|
putDeleteManyCiphersAdmin(request: CipherBulkDeleteRequest): Promise<any> {
|
|
return this.send("PUT", "/ciphers/delete-admin", request, true, false);
|
|
}
|
|
|
|
async putRestoreCipher(id: string): Promise<CipherResponse> {
|
|
const r = await this.send("PUT", "/ciphers/" + id + "/restore", null, true, true);
|
|
return new CipherResponse(r);
|
|
}
|
|
|
|
async putRestoreCipherAdmin(id: string): Promise<CipherResponse> {
|
|
const r = await this.send("PUT", "/ciphers/" + id + "/restore-admin", null, true, true);
|
|
return new CipherResponse(r);
|
|
}
|
|
|
|
async putRestoreManyCiphers(
|
|
request: CipherBulkRestoreRequest,
|
|
): Promise<ListResponse<CipherResponse>> {
|
|
const r = await this.send("PUT", "/ciphers/restore", request, true, true);
|
|
return new ListResponse<CipherResponse>(r, CipherResponse);
|
|
}
|
|
|
|
async putRestoreManyCiphersAdmin(
|
|
request: CipherBulkRestoreRequest,
|
|
): Promise<ListResponse<CipherResponse>> {
|
|
const r = await this.send("PUT", "/ciphers/restore-admin", request, true, true);
|
|
return new ListResponse<CipherResponse>(r, CipherResponse);
|
|
}
|
|
|
|
// Attachments APIs
|
|
|
|
async getAttachmentData(
|
|
cipherId: string,
|
|
attachmentId: string,
|
|
emergencyAccessId?: string,
|
|
): Promise<AttachmentResponse> {
|
|
const path =
|
|
(emergencyAccessId != null ? "/emergency-access/" + emergencyAccessId + "/" : "/ciphers/") +
|
|
cipherId +
|
|
"/attachment/" +
|
|
attachmentId;
|
|
const r = await this.send("GET", path, null, true, true);
|
|
return new AttachmentResponse(r);
|
|
}
|
|
|
|
async postCipherAttachment(
|
|
id: string,
|
|
request: AttachmentRequest,
|
|
): Promise<AttachmentUploadDataResponse> {
|
|
const r = await this.send("POST", "/ciphers/" + id + "/attachment/v2", request, true, true);
|
|
return new AttachmentUploadDataResponse(r);
|
|
}
|
|
|
|
/**
|
|
* @deprecated Mar 25 2021: This method has been deprecated in favor of direct uploads.
|
|
* This method still exists for backward compatibility with old server versions.
|
|
*/
|
|
async postCipherAttachmentLegacy(id: string, data: FormData): Promise<CipherResponse> {
|
|
const r = await this.send("POST", "/ciphers/" + id + "/attachment", data, true, true);
|
|
return new CipherResponse(r);
|
|
}
|
|
|
|
/**
|
|
* @deprecated Mar 25 2021: This method has been deprecated in favor of direct uploads.
|
|
* This method still exists for backward compatibility with old server versions.
|
|
*/
|
|
async postCipherAttachmentAdminLegacy(id: string, data: FormData): Promise<CipherResponse> {
|
|
const r = await this.send("POST", "/ciphers/" + id + "/attachment-admin", data, true, true);
|
|
return new CipherResponse(r);
|
|
}
|
|
|
|
deleteCipherAttachment(id: string, attachmentId: string): Promise<any> {
|
|
return this.send("DELETE", "/ciphers/" + id + "/attachment/" + attachmentId, null, true, false);
|
|
}
|
|
|
|
deleteCipherAttachmentAdmin(id: string, attachmentId: string): Promise<any> {
|
|
return this.send(
|
|
"DELETE",
|
|
"/ciphers/" + id + "/attachment/" + attachmentId + "/admin",
|
|
null,
|
|
true,
|
|
false,
|
|
);
|
|
}
|
|
|
|
postShareCipherAttachment(
|
|
id: string,
|
|
attachmentId: string,
|
|
data: FormData,
|
|
organizationId: string,
|
|
): Promise<any> {
|
|
return this.send(
|
|
"POST",
|
|
"/ciphers/" + id + "/attachment/" + attachmentId + "/share?organizationId=" + organizationId,
|
|
data,
|
|
true,
|
|
false,
|
|
);
|
|
}
|
|
|
|
async renewAttachmentUploadUrl(
|
|
id: string,
|
|
attachmentId: string,
|
|
): Promise<AttachmentUploadDataResponse> {
|
|
const r = await this.send(
|
|
"GET",
|
|
"/ciphers/" + id + "/attachment/" + attachmentId + "/renew",
|
|
null,
|
|
true,
|
|
true,
|
|
);
|
|
return new AttachmentUploadDataResponse(r);
|
|
}
|
|
|
|
postAttachmentFile(id: string, attachmentId: string, data: FormData): Promise<any> {
|
|
return this.send("POST", "/ciphers/" + id + "/attachment/" + attachmentId, data, true, false);
|
|
}
|
|
|
|
// Collections APIs
|
|
|
|
async getCollectionAccessDetails(
|
|
organizationId: string,
|
|
id: string,
|
|
): Promise<CollectionAccessDetailsResponse> {
|
|
const r = await this.send(
|
|
"GET",
|
|
"/organizations/" + organizationId + "/collections/" + id + "/details",
|
|
null,
|
|
true,
|
|
true,
|
|
);
|
|
return new CollectionAccessDetailsResponse(r);
|
|
}
|
|
|
|
async getUserCollections(): Promise<ListResponse<CollectionResponse>> {
|
|
const r = await this.send("GET", "/collections", null, true, true);
|
|
return new ListResponse(r, CollectionResponse);
|
|
}
|
|
|
|
async getCollections(organizationId: string): Promise<ListResponse<CollectionResponse>> {
|
|
const r = await this.send(
|
|
"GET",
|
|
"/organizations/" + organizationId + "/collections",
|
|
null,
|
|
true,
|
|
true,
|
|
);
|
|
return new ListResponse(r, CollectionResponse);
|
|
}
|
|
|
|
async getManyCollectionsWithAccessDetails(
|
|
organizationId: string,
|
|
): Promise<ListResponse<CollectionAccessDetailsResponse>> {
|
|
const r = await this.send(
|
|
"GET",
|
|
"/organizations/" + organizationId + "/collections/details",
|
|
null,
|
|
true,
|
|
true,
|
|
);
|
|
return new ListResponse(r, CollectionAccessDetailsResponse);
|
|
}
|
|
|
|
async getCollectionUsers(
|
|
organizationId: string,
|
|
id: string,
|
|
): Promise<SelectionReadOnlyResponse[]> {
|
|
const r = await this.send(
|
|
"GET",
|
|
"/organizations/" + organizationId + "/collections/" + id + "/users",
|
|
null,
|
|
true,
|
|
true,
|
|
);
|
|
return r.map((dr: any) => new SelectionReadOnlyResponse(dr));
|
|
}
|
|
|
|
async postCollection(
|
|
organizationId: string,
|
|
request: CollectionRequest,
|
|
): Promise<CollectionDetailsResponse> {
|
|
const r = await this.send(
|
|
"POST",
|
|
"/organizations/" + organizationId + "/collections",
|
|
request,
|
|
true,
|
|
true,
|
|
);
|
|
return new CollectionDetailsResponse(r);
|
|
}
|
|
|
|
async putCollection(
|
|
organizationId: string,
|
|
id: string,
|
|
request: CollectionRequest,
|
|
): Promise<CollectionDetailsResponse> {
|
|
const r = await this.send(
|
|
"PUT",
|
|
"/organizations/" + organizationId + "/collections/" + id,
|
|
request,
|
|
true,
|
|
true,
|
|
);
|
|
return new CollectionDetailsResponse(r);
|
|
}
|
|
|
|
async putCollectionUsers(
|
|
organizationId: string,
|
|
id: string,
|
|
request: SelectionReadOnlyRequest[],
|
|
): Promise<any> {
|
|
await this.send(
|
|
"PUT",
|
|
"/organizations/" + organizationId + "/collections/" + id + "/users",
|
|
request,
|
|
true,
|
|
false,
|
|
);
|
|
}
|
|
|
|
deleteCollection(organizationId: string, id: string): Promise<any> {
|
|
return this.send(
|
|
"DELETE",
|
|
"/organizations/" + organizationId + "/collections/" + id,
|
|
null,
|
|
true,
|
|
false,
|
|
);
|
|
}
|
|
|
|
deleteManyCollections(organizationId: string, collectionIds: string[]): Promise<any> {
|
|
return this.send(
|
|
"DELETE",
|
|
"/organizations/" + organizationId + "/collections",
|
|
new CollectionBulkDeleteRequest(collectionIds),
|
|
true,
|
|
false,
|
|
);
|
|
}
|
|
|
|
deleteCollectionUser(
|
|
organizationId: string,
|
|
id: string,
|
|
organizationUserId: string,
|
|
): Promise<any> {
|
|
return this.send(
|
|
"DELETE",
|
|
"/organizations/" + organizationId + "/collections/" + id + "/user/" + organizationUserId,
|
|
null,
|
|
true,
|
|
false,
|
|
);
|
|
}
|
|
|
|
// Groups APIs
|
|
|
|
async getGroupUsers(organizationId: string, id: string): Promise<string[]> {
|
|
const r = await this.send(
|
|
"GET",
|
|
"/organizations/" + organizationId + "/groups/" + id + "/users",
|
|
null,
|
|
true,
|
|
true,
|
|
);
|
|
return r;
|
|
}
|
|
|
|
async putGroupUsers(organizationId: string, id: string, request: string[]): Promise<any> {
|
|
await this.send(
|
|
"PUT",
|
|
"/organizations/" + organizationId + "/groups/" + id + "/users",
|
|
request,
|
|
true,
|
|
false,
|
|
);
|
|
}
|
|
|
|
deleteGroupUser(organizationId: string, id: string, organizationUserId: string): Promise<any> {
|
|
return this.send(
|
|
"DELETE",
|
|
"/organizations/" + organizationId + "/groups/" + id + "/user/" + organizationUserId,
|
|
null,
|
|
true,
|
|
false,
|
|
);
|
|
}
|
|
|
|
// Plan APIs
|
|
|
|
async getPlans(): Promise<ListResponse<PlanResponse>> {
|
|
const r = await this.send("GET", "/plans", null, false, true);
|
|
return new ListResponse(r, PlanResponse);
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
// Settings APIs
|
|
|
|
async getSettingsDomains(): Promise<DomainsResponse> {
|
|
const r = await this.send("GET", "/settings/domains", null, true, true);
|
|
return new DomainsResponse(r);
|
|
}
|
|
|
|
async putSettingsDomains(request: UpdateDomainsRequest): Promise<DomainsResponse> {
|
|
const r = await this.send("PUT", "/settings/domains", request, true, true);
|
|
return new DomainsResponse(r);
|
|
}
|
|
|
|
// Sync APIs
|
|
|
|
async getSync(): Promise<SyncResponse> {
|
|
const path = this.isDesktopClient || this.isWebClient ? "/sync?excludeDomains=true" : "/sync";
|
|
const r = await this.send("GET", path, null, true, true);
|
|
return new SyncResponse(r);
|
|
}
|
|
|
|
// Two-factor APIs
|
|
|
|
async getTwoFactorProviders(): Promise<ListResponse<TwoFactorProviderResponse>> {
|
|
const r = await this.send("GET", "/two-factor", null, true, true);
|
|
return new ListResponse(r, TwoFactorProviderResponse);
|
|
}
|
|
|
|
async getTwoFactorOrganizationProviders(
|
|
organizationId: string,
|
|
): Promise<ListResponse<TwoFactorProviderResponse>> {
|
|
const r = await this.send(
|
|
"GET",
|
|
"/organizations/" + organizationId + "/two-factor",
|
|
null,
|
|
true,
|
|
true,
|
|
);
|
|
return new ListResponse(r, TwoFactorProviderResponse);
|
|
}
|
|
|
|
async getTwoFactorAuthenticator(
|
|
request: SecretVerificationRequest,
|
|
): Promise<TwoFactorAuthenticatorResponse> {
|
|
const r = await this.send("POST", "/two-factor/get-authenticator", request, true, true);
|
|
return new TwoFactorAuthenticatorResponse(r);
|
|
}
|
|
|
|
async getTwoFactorEmail(request: SecretVerificationRequest): Promise<TwoFactorEmailResponse> {
|
|
const r = await this.send("POST", "/two-factor/get-email", request, true, true);
|
|
return new TwoFactorEmailResponse(r);
|
|
}
|
|
|
|
async getTwoFactorDuo(request: SecretVerificationRequest): Promise<TwoFactorDuoResponse> {
|
|
const r = await this.send("POST", "/two-factor/get-duo", request, true, true);
|
|
return new TwoFactorDuoResponse(r);
|
|
}
|
|
|
|
async getTwoFactorOrganizationDuo(
|
|
organizationId: string,
|
|
request: SecretVerificationRequest,
|
|
): Promise<TwoFactorDuoResponse> {
|
|
const r = await this.send(
|
|
"POST",
|
|
"/organizations/" + organizationId + "/two-factor/get-duo",
|
|
request,
|
|
true,
|
|
true,
|
|
);
|
|
return new TwoFactorDuoResponse(r);
|
|
}
|
|
|
|
async getTwoFactorYubiKey(request: SecretVerificationRequest): Promise<TwoFactorYubiKeyResponse> {
|
|
const r = await this.send("POST", "/two-factor/get-yubikey", request, true, true);
|
|
return new TwoFactorYubiKeyResponse(r);
|
|
}
|
|
|
|
async getTwoFactorWebAuthn(
|
|
request: SecretVerificationRequest,
|
|
): Promise<TwoFactorWebAuthnResponse> {
|
|
const r = await this.send("POST", "/two-factor/get-webauthn", request, true, true);
|
|
return new TwoFactorWebAuthnResponse(r);
|
|
}
|
|
|
|
async getTwoFactorWebAuthnChallenge(
|
|
request: SecretVerificationRequest,
|
|
): Promise<ChallengeResponse> {
|
|
const r = await this.send("POST", "/two-factor/get-webauthn-challenge", request, true, true);
|
|
return new ChallengeResponse(r);
|
|
}
|
|
|
|
async getTwoFactorRecover(request: SecretVerificationRequest): Promise<TwoFactorRecoverResponse> {
|
|
const r = await this.send("POST", "/two-factor/get-recover", request, true, true);
|
|
return new TwoFactorRecoverResponse(r);
|
|
}
|
|
|
|
async putTwoFactorAuthenticator(
|
|
request: UpdateTwoFactorAuthenticatorRequest,
|
|
): Promise<TwoFactorAuthenticatorResponse> {
|
|
const r = await this.send("PUT", "/two-factor/authenticator", request, true, true);
|
|
return new TwoFactorAuthenticatorResponse(r);
|
|
}
|
|
|
|
async putTwoFactorEmail(request: UpdateTwoFactorEmailRequest): Promise<TwoFactorEmailResponse> {
|
|
const r = await this.send("PUT", "/two-factor/email", request, true, true);
|
|
return new TwoFactorEmailResponse(r);
|
|
}
|
|
|
|
async putTwoFactorDuo(request: UpdateTwoFactorDuoRequest): Promise<TwoFactorDuoResponse> {
|
|
const r = await this.send("PUT", "/two-factor/duo", request, true, true);
|
|
return new TwoFactorDuoResponse(r);
|
|
}
|
|
|
|
async putTwoFactorOrganizationDuo(
|
|
organizationId: string,
|
|
request: UpdateTwoFactorDuoRequest,
|
|
): Promise<TwoFactorDuoResponse> {
|
|
const r = await this.send(
|
|
"PUT",
|
|
"/organizations/" + organizationId + "/two-factor/duo",
|
|
request,
|
|
true,
|
|
true,
|
|
);
|
|
return new TwoFactorDuoResponse(r);
|
|
}
|
|
|
|
async putTwoFactorYubiKey(
|
|
request: UpdateTwoFactorYubioOtpRequest,
|
|
): Promise<TwoFactorYubiKeyResponse> {
|
|
const r = await this.send("PUT", "/two-factor/yubikey", request, true, true);
|
|
return new TwoFactorYubiKeyResponse(r);
|
|
}
|
|
|
|
async putTwoFactorWebAuthn(
|
|
request: UpdateTwoFactorWebAuthnRequest,
|
|
): Promise<TwoFactorWebAuthnResponse> {
|
|
const response = request.deviceResponse.response as AuthenticatorAttestationResponse;
|
|
const data: any = Object.assign({}, request);
|
|
|
|
data.deviceResponse = {
|
|
id: request.deviceResponse.id,
|
|
rawId: btoa(request.deviceResponse.id),
|
|
type: request.deviceResponse.type,
|
|
extensions: request.deviceResponse.getClientExtensionResults(),
|
|
response: {
|
|
AttestationObject: Utils.fromBufferToB64(response.attestationObject),
|
|
clientDataJson: Utils.fromBufferToB64(response.clientDataJSON),
|
|
},
|
|
};
|
|
|
|
const r = await this.send("PUT", "/two-factor/webauthn", data, true, true);
|
|
return new TwoFactorWebAuthnResponse(r);
|
|
}
|
|
|
|
async deleteTwoFactorWebAuthn(
|
|
request: UpdateTwoFactorWebAuthnDeleteRequest,
|
|
): Promise<TwoFactorWebAuthnResponse> {
|
|
const r = await this.send("DELETE", "/two-factor/webauthn", request, true, true);
|
|
return new TwoFactorWebAuthnResponse(r);
|
|
}
|
|
|
|
async putTwoFactorDisable(request: TwoFactorProviderRequest): Promise<TwoFactorProviderResponse> {
|
|
const r = await this.send("PUT", "/two-factor/disable", request, true, true);
|
|
return new TwoFactorProviderResponse(r);
|
|
}
|
|
|
|
async putTwoFactorOrganizationDisable(
|
|
organizationId: string,
|
|
request: TwoFactorProviderRequest,
|
|
): Promise<TwoFactorProviderResponse> {
|
|
const r = await this.send(
|
|
"PUT",
|
|
"/organizations/" + organizationId + "/two-factor/disable",
|
|
request,
|
|
true,
|
|
true,
|
|
);
|
|
return new TwoFactorProviderResponse(r);
|
|
}
|
|
|
|
postTwoFactorRecover(request: TwoFactorRecoveryRequest): Promise<any> {
|
|
return this.send("POST", "/two-factor/recover", request, false, false);
|
|
}
|
|
|
|
postTwoFactorEmailSetup(request: TwoFactorEmailRequest): Promise<any> {
|
|
return this.send("POST", "/two-factor/send-email", request, true, false);
|
|
}
|
|
|
|
postTwoFactorEmail(request: TwoFactorEmailRequest): Promise<any> {
|
|
return this.send("POST", "/two-factor/send-email-login", request, false, false);
|
|
}
|
|
|
|
async getDeviceVerificationSettings(): Promise<DeviceVerificationResponse> {
|
|
const r = await this.send(
|
|
"GET",
|
|
"/two-factor/get-device-verification-settings",
|
|
null,
|
|
true,
|
|
true,
|
|
);
|
|
return new DeviceVerificationResponse(r);
|
|
}
|
|
|
|
async putDeviceVerificationSettings(
|
|
request: DeviceVerificationRequest,
|
|
): Promise<DeviceVerificationResponse> {
|
|
const r = await this.send(
|
|
"PUT",
|
|
"/two-factor/device-verification-settings",
|
|
request,
|
|
true,
|
|
true,
|
|
);
|
|
return new DeviceVerificationResponse(r);
|
|
}
|
|
|
|
// Organization APIs
|
|
|
|
async getCloudCommunicationsEnabled(): Promise<boolean> {
|
|
const r = await this.send("GET", "/organizations/connections/enabled", null, true, true);
|
|
return r as boolean;
|
|
}
|
|
|
|
async getOrganizationConnection<TConfig extends OrganizationConnectionConfigApis>(
|
|
id: string,
|
|
type: OrganizationConnectionType,
|
|
configType: { new (response: any): TConfig },
|
|
): Promise<OrganizationConnectionResponse<TConfig>> {
|
|
const r = await this.send("GET", `/organizations/connections/${id}/${type}`, null, true, true);
|
|
return new OrganizationConnectionResponse(r, configType);
|
|
}
|
|
|
|
async createOrganizationConnection<TConfig extends OrganizationConnectionConfigApis>(
|
|
request: OrganizationConnectionRequest,
|
|
configType: { new (response: any): TConfig },
|
|
): Promise<OrganizationConnectionResponse<TConfig>> {
|
|
const r = await this.send("POST", "/organizations/connections/", request, true, true);
|
|
return new OrganizationConnectionResponse(r, configType);
|
|
}
|
|
|
|
async updateOrganizationConnection<TConfig extends OrganizationConnectionConfigApis>(
|
|
request: OrganizationConnectionRequest,
|
|
configType: { new (response: any): TConfig },
|
|
organizationConnectionId?: string,
|
|
): Promise<OrganizationConnectionResponse<TConfig>> {
|
|
const r = await this.send(
|
|
"PUT",
|
|
"/organizations/connections/" + organizationConnectionId,
|
|
request,
|
|
true,
|
|
true,
|
|
);
|
|
return new OrganizationConnectionResponse(r, configType);
|
|
}
|
|
|
|
async deleteOrganizationConnection(id: string): Promise<void> {
|
|
return this.send("DELETE", "/organizations/connections/" + id, null, true, false);
|
|
}
|
|
|
|
// Provider APIs
|
|
|
|
async postProviderSetup(id: string, request: ProviderSetupRequest) {
|
|
const r = await this.send("POST", "/providers/" + id + "/setup", request, true, true);
|
|
return new ProviderResponse(r);
|
|
}
|
|
|
|
async getProvider(id: string) {
|
|
const r = await this.send("GET", "/providers/" + id, null, true, true);
|
|
return new ProviderResponse(r);
|
|
}
|
|
|
|
async putProvider(id: string, request: ProviderUpdateRequest) {
|
|
const r = await this.send("PUT", "/providers/" + id, request, true, true);
|
|
return new ProviderResponse(r);
|
|
}
|
|
|
|
// Provider User APIs
|
|
|
|
async getProviderUsers(
|
|
providerId: string,
|
|
): Promise<ListResponse<ProviderUserUserDetailsResponse>> {
|
|
const r = await this.send("GET", "/providers/" + providerId + "/users", null, true, true);
|
|
return new ListResponse(r, ProviderUserUserDetailsResponse);
|
|
}
|
|
|
|
async getProviderUser(providerId: string, id: string): Promise<ProviderUserResponse> {
|
|
const r = await this.send("GET", "/providers/" + providerId + "/users/" + id, null, true, true);
|
|
return new ProviderUserResponse(r);
|
|
}
|
|
|
|
postProviderUserInvite(providerId: string, request: ProviderUserInviteRequest): Promise<any> {
|
|
return this.send("POST", "/providers/" + providerId + "/users/invite", request, true, false);
|
|
}
|
|
|
|
postProviderUserReinvite(providerId: string, id: string): Promise<any> {
|
|
return this.send(
|
|
"POST",
|
|
"/providers/" + providerId + "/users/" + id + "/reinvite",
|
|
null,
|
|
true,
|
|
false,
|
|
);
|
|
}
|
|
|
|
async postManyProviderUserReinvite(
|
|
providerId: string,
|
|
request: ProviderUserBulkRequest,
|
|
): Promise<ListResponse<ProviderUserBulkResponse>> {
|
|
const r = await this.send(
|
|
"POST",
|
|
"/providers/" + providerId + "/users/reinvite",
|
|
request,
|
|
true,
|
|
true,
|
|
);
|
|
return new ListResponse(r, ProviderUserBulkResponse);
|
|
}
|
|
|
|
async postProviderUserBulkConfirm(
|
|
providerId: string,
|
|
request: ProviderUserBulkConfirmRequest,
|
|
): Promise<ListResponse<ProviderUserBulkResponse>> {
|
|
const r = await this.send(
|
|
"POST",
|
|
"/providers/" + providerId + "/users/confirm",
|
|
request,
|
|
true,
|
|
true,
|
|
);
|
|
return new ListResponse(r, ProviderUserBulkResponse);
|
|
}
|
|
|
|
async deleteManyProviderUsers(
|
|
providerId: string,
|
|
request: ProviderUserBulkRequest,
|
|
): Promise<ListResponse<ProviderUserBulkResponse>> {
|
|
const r = await this.send("DELETE", "/providers/" + providerId + "/users", request, true, true);
|
|
return new ListResponse(r, ProviderUserBulkResponse);
|
|
}
|
|
|
|
postProviderUserAccept(
|
|
providerId: string,
|
|
id: string,
|
|
request: ProviderUserAcceptRequest,
|
|
): Promise<any> {
|
|
return this.send(
|
|
"POST",
|
|
"/providers/" + providerId + "/users/" + id + "/accept",
|
|
request,
|
|
true,
|
|
false,
|
|
);
|
|
}
|
|
|
|
postProviderUserConfirm(
|
|
providerId: string,
|
|
id: string,
|
|
request: ProviderUserConfirmRequest,
|
|
): Promise<any> {
|
|
return this.send(
|
|
"POST",
|
|
"/providers/" + providerId + "/users/" + id + "/confirm",
|
|
request,
|
|
true,
|
|
false,
|
|
);
|
|
}
|
|
|
|
async postProviderUsersPublicKey(
|
|
providerId: string,
|
|
request: ProviderUserBulkRequest,
|
|
): Promise<ListResponse<ProviderUserBulkPublicKeyResponse>> {
|
|
const r = await this.send(
|
|
"POST",
|
|
"/providers/" + providerId + "/users/public-keys",
|
|
request,
|
|
true,
|
|
true,
|
|
);
|
|
return new ListResponse(r, ProviderUserBulkPublicKeyResponse);
|
|
}
|
|
|
|
putProviderUser(
|
|
providerId: string,
|
|
id: string,
|
|
request: ProviderUserUpdateRequest,
|
|
): Promise<any> {
|
|
return this.send("PUT", "/providers/" + providerId + "/users/" + id, request, true, false);
|
|
}
|
|
|
|
deleteProviderUser(providerId: string, id: string): Promise<any> {
|
|
return this.send("DELETE", "/providers/" + providerId + "/users/" + id, null, true, false);
|
|
}
|
|
|
|
// Provider Organization APIs
|
|
|
|
async getProviderClients(
|
|
providerId: string,
|
|
): Promise<ListResponse<ProviderOrganizationOrganizationDetailsResponse>> {
|
|
const r = await this.send(
|
|
"GET",
|
|
"/providers/" + providerId + "/organizations",
|
|
null,
|
|
true,
|
|
true,
|
|
);
|
|
return new ListResponse(r, ProviderOrganizationOrganizationDetailsResponse);
|
|
}
|
|
|
|
postProviderAddOrganization(
|
|
providerId: string,
|
|
request: ProviderAddOrganizationRequest,
|
|
): Promise<any> {
|
|
return this.send(
|
|
"POST",
|
|
"/providers/" + providerId + "/organizations/add",
|
|
request,
|
|
true,
|
|
false,
|
|
);
|
|
}
|
|
|
|
async postProviderCreateOrganization(
|
|
providerId: string,
|
|
request: ProviderOrganizationCreateRequest,
|
|
): Promise<ProviderOrganizationResponse> {
|
|
const r = await this.send(
|
|
"POST",
|
|
"/providers/" + providerId + "/organizations",
|
|
request,
|
|
true,
|
|
true,
|
|
);
|
|
return new ProviderOrganizationResponse(r);
|
|
}
|
|
|
|
deleteProviderOrganization(providerId: string, id: string): Promise<any> {
|
|
return this.send(
|
|
"DELETE",
|
|
"/providers/" + providerId + "/organizations/" + id,
|
|
null,
|
|
true,
|
|
false,
|
|
);
|
|
}
|
|
|
|
// Event APIs
|
|
|
|
async getEvents(start: string, end: string, token: string): Promise<ListResponse<EventResponse>> {
|
|
const r = await this.send(
|
|
"GET",
|
|
this.addEventParameters("/events", start, end, token),
|
|
null,
|
|
true,
|
|
true,
|
|
);
|
|
return new ListResponse(r, EventResponse);
|
|
}
|
|
|
|
async getEventsCipher(
|
|
id: string,
|
|
start: string,
|
|
end: string,
|
|
token: string,
|
|
): Promise<ListResponse<EventResponse>> {
|
|
const r = await this.send(
|
|
"GET",
|
|
this.addEventParameters("/ciphers/" + id + "/events", start, end, token),
|
|
null,
|
|
true,
|
|
true,
|
|
);
|
|
return new ListResponse(r, EventResponse);
|
|
}
|
|
|
|
async getEventsOrganization(
|
|
id: string,
|
|
start: string,
|
|
end: string,
|
|
token: string,
|
|
): Promise<ListResponse<EventResponse>> {
|
|
const r = await this.send(
|
|
"GET",
|
|
this.addEventParameters("/organizations/" + id + "/events", start, end, token),
|
|
null,
|
|
true,
|
|
true,
|
|
);
|
|
return new ListResponse(r, EventResponse);
|
|
}
|
|
|
|
async getEventsOrganizationUser(
|
|
organizationId: string,
|
|
id: string,
|
|
start: string,
|
|
end: string,
|
|
token: string,
|
|
): Promise<ListResponse<EventResponse>> {
|
|
const r = await this.send(
|
|
"GET",
|
|
this.addEventParameters(
|
|
"/organizations/" + organizationId + "/users/" + id + "/events",
|
|
start,
|
|
end,
|
|
token,
|
|
),
|
|
null,
|
|
true,
|
|
true,
|
|
);
|
|
return new ListResponse(r, EventResponse);
|
|
}
|
|
|
|
async getEventsProvider(
|
|
id: string,
|
|
start: string,
|
|
end: string,
|
|
token: string,
|
|
): Promise<ListResponse<EventResponse>> {
|
|
const r = await this.send(
|
|
"GET",
|
|
this.addEventParameters("/providers/" + id + "/events", start, end, token),
|
|
null,
|
|
true,
|
|
true,
|
|
);
|
|
return new ListResponse(r, EventResponse);
|
|
}
|
|
|
|
async getEventsProviderUser(
|
|
providerId: string,
|
|
id: string,
|
|
start: string,
|
|
end: string,
|
|
token: string,
|
|
): Promise<ListResponse<EventResponse>> {
|
|
const r = await this.send(
|
|
"GET",
|
|
this.addEventParameters(
|
|
"/providers/" + providerId + "/users/" + id + "/events",
|
|
start,
|
|
end,
|
|
token,
|
|
),
|
|
null,
|
|
true,
|
|
true,
|
|
);
|
|
return new ListResponse(r, EventResponse);
|
|
}
|
|
|
|
async postEventsCollect(request: EventRequest[]): Promise<any> {
|
|
const authHeader = await this.getActiveBearerToken();
|
|
const headers = new Headers({
|
|
"Device-Type": this.deviceType,
|
|
Authorization: "Bearer " + authHeader,
|
|
"Content-Type": "application/json; charset=utf-8",
|
|
});
|
|
if (this.customUserAgent != null) {
|
|
headers.set("User-Agent", this.customUserAgent);
|
|
}
|
|
const response = await this.fetch(
|
|
new Request(this.environmentService.getEventsUrl() + "/collect", {
|
|
cache: "no-store",
|
|
credentials: this.getCredentials(),
|
|
method: "POST",
|
|
body: JSON.stringify(request),
|
|
headers: headers,
|
|
}),
|
|
);
|
|
if (response.status !== 200) {
|
|
return Promise.reject("Event post failed.");
|
|
}
|
|
}
|
|
|
|
// User APIs
|
|
|
|
async getUserPublicKey(id: string): Promise<UserKeyResponse> {
|
|
const r = await this.send("GET", "/users/" + id + "/public-key", null, true, true);
|
|
return new UserKeyResponse(r);
|
|
}
|
|
|
|
// HIBP APIs
|
|
|
|
async getHibpBreach(username: string): Promise<BreachAccountResponse[]> {
|
|
const r = await this.send("GET", "/hibp/breach?username=" + username, null, true, true);
|
|
return r.map((a: any) => new BreachAccountResponse(a));
|
|
}
|
|
|
|
// Misc
|
|
|
|
async postBitPayInvoice(request: BitPayInvoiceRequest): Promise<string> {
|
|
const r = await this.send("POST", "/bitpay-invoice", request, true, true);
|
|
return r as string;
|
|
}
|
|
|
|
async postSetupPayment(): Promise<string> {
|
|
const r = await this.send("POST", "/setup-payment", null, true, true);
|
|
return r as string;
|
|
}
|
|
|
|
// Key Connector
|
|
|
|
async getMasterKeyFromKeyConnector(
|
|
keyConnectorUrl: string,
|
|
): Promise<KeyConnectorUserKeyResponse> {
|
|
const authHeader = await this.getActiveBearerToken();
|
|
|
|
const response = await this.fetch(
|
|
new Request(keyConnectorUrl + "/user-keys", {
|
|
cache: "no-store",
|
|
method: "GET",
|
|
headers: new Headers({
|
|
Accept: "application/json",
|
|
Authorization: "Bearer " + authHeader,
|
|
}),
|
|
}),
|
|
);
|
|
|
|
if (response.status !== 200) {
|
|
const error = await this.handleError(response, false, true);
|
|
return Promise.reject(error);
|
|
}
|
|
|
|
return new KeyConnectorUserKeyResponse(await response.json());
|
|
}
|
|
|
|
async postUserKeyToKeyConnector(
|
|
keyConnectorUrl: string,
|
|
request: KeyConnectorUserKeyRequest,
|
|
): Promise<void> {
|
|
const authHeader = await this.getActiveBearerToken();
|
|
|
|
const response = await this.fetch(
|
|
new Request(keyConnectorUrl + "/user-keys", {
|
|
cache: "no-store",
|
|
method: "POST",
|
|
headers: new Headers({
|
|
Accept: "application/json",
|
|
Authorization: "Bearer " + authHeader,
|
|
"Content-Type": "application/json; charset=utf-8",
|
|
}),
|
|
body: JSON.stringify(request),
|
|
}),
|
|
);
|
|
|
|
if (response.status !== 200) {
|
|
const error = await this.handleError(response, false, true);
|
|
return Promise.reject(error);
|
|
}
|
|
}
|
|
|
|
async getKeyConnectorAlive(keyConnectorUrl: string) {
|
|
const response = await this.fetch(
|
|
new Request(keyConnectorUrl + "/alive", {
|
|
cache: "no-store",
|
|
method: "GET",
|
|
headers: new Headers({
|
|
Accept: "application/json",
|
|
"Content-Type": "application/json; charset=utf-8",
|
|
}),
|
|
}),
|
|
);
|
|
|
|
if (response.status !== 200) {
|
|
const error = await this.handleError(response, false, true);
|
|
return Promise.reject(error);
|
|
}
|
|
}
|
|
|
|
async getOrganizationExport(organizationId: string): Promise<OrganizationExportResponse> {
|
|
const r = await this.send(
|
|
"GET",
|
|
"/organizations/" + organizationId + "/export",
|
|
null,
|
|
true,
|
|
true,
|
|
);
|
|
return new OrganizationExportResponse(r);
|
|
}
|
|
|
|
// Helpers
|
|
|
|
async getActiveBearerToken(): Promise<string> {
|
|
let accessToken = await this.tokenService.getAccessToken();
|
|
if (await this.tokenService.tokenNeedsRefresh()) {
|
|
await this.doAuthRefresh();
|
|
accessToken = await this.tokenService.getAccessToken();
|
|
}
|
|
return accessToken;
|
|
}
|
|
|
|
async fetch(request: Request): Promise<Response> {
|
|
if (request.method === "GET") {
|
|
request.headers.set("Cache-Control", "no-store");
|
|
request.headers.set("Pragma", "no-cache");
|
|
}
|
|
request.headers.set("Bitwarden-Client-Name", this.platformUtilsService.getClientType());
|
|
request.headers.set(
|
|
"Bitwarden-Client-Version",
|
|
await this.platformUtilsService.getApplicationVersionNumber(),
|
|
);
|
|
return this.nativeFetch(request);
|
|
}
|
|
|
|
nativeFetch(request: Request): Promise<Response> {
|
|
return fetch(request);
|
|
}
|
|
|
|
async preValidateSso(identifier: string): Promise<SsoPreValidateResponse> {
|
|
if (identifier == null || identifier === "") {
|
|
throw new Error("Organization Identifier was not provided.");
|
|
}
|
|
const headers = new Headers({
|
|
Accept: "application/json",
|
|
"Device-Type": this.deviceType,
|
|
});
|
|
if (this.customUserAgent != null) {
|
|
headers.set("User-Agent", this.customUserAgent);
|
|
}
|
|
|
|
const path = `/sso/prevalidate?domainHint=${encodeURIComponent(identifier)}`;
|
|
const response = await this.fetch(
|
|
new Request(this.environmentService.getIdentityUrl() + path, {
|
|
cache: "no-store",
|
|
credentials: this.getCredentials(),
|
|
headers: headers,
|
|
method: "GET",
|
|
}),
|
|
);
|
|
|
|
if (response.status === 200) {
|
|
const body = await response.json();
|
|
return new SsoPreValidateResponse(body);
|
|
} else {
|
|
const error = await this.handleError(response, false, true);
|
|
return Promise.reject(error);
|
|
}
|
|
}
|
|
|
|
async postCreateSponsorship(
|
|
sponsoredOrgId: string,
|
|
request: OrganizationSponsorshipCreateRequest,
|
|
): Promise<void> {
|
|
return await this.send(
|
|
"POST",
|
|
"/organization/sponsorship/" +
|
|
(this.platformUtilsService.isSelfHost() ? "self-hosted/" : "") +
|
|
sponsoredOrgId +
|
|
"/families-for-enterprise",
|
|
request,
|
|
true,
|
|
false,
|
|
);
|
|
}
|
|
|
|
async getSponsorshipSyncStatus(
|
|
sponsoredOrgId: string,
|
|
): Promise<OrganizationSponsorshipSyncStatusResponse> {
|
|
const response = await this.send(
|
|
"GET",
|
|
"/organization/sponsorship/" + sponsoredOrgId + "/sync-status",
|
|
null,
|
|
true,
|
|
true,
|
|
);
|
|
return new OrganizationSponsorshipSyncStatusResponse(response);
|
|
}
|
|
|
|
async deleteRevokeSponsorship(sponsoringOrganizationId: string): Promise<void> {
|
|
return await this.send(
|
|
"DELETE",
|
|
"/organization/sponsorship/" +
|
|
(this.platformUtilsService.isSelfHost() ? "self-hosted/" : "") +
|
|
sponsoringOrganizationId,
|
|
null,
|
|
true,
|
|
false,
|
|
);
|
|
}
|
|
|
|
async deleteRemoveSponsorship(sponsoringOrgId: string): Promise<void> {
|
|
return await this.send(
|
|
"DELETE",
|
|
"/organization/sponsorship/sponsored/" + sponsoringOrgId,
|
|
null,
|
|
true,
|
|
false,
|
|
);
|
|
}
|
|
|
|
async postPreValidateSponsorshipToken(sponsorshipToken: string): Promise<boolean> {
|
|
const r = await this.send(
|
|
"POST",
|
|
"/organization/sponsorship/validate-token?sponsorshipToken=" +
|
|
encodeURIComponent(sponsorshipToken),
|
|
null,
|
|
true,
|
|
true,
|
|
);
|
|
return r as boolean;
|
|
}
|
|
|
|
async postRedeemSponsorship(
|
|
sponsorshipToken: string,
|
|
request: OrganizationSponsorshipRedeemRequest,
|
|
): Promise<void> {
|
|
return await this.send(
|
|
"POST",
|
|
"/organization/sponsorship/redeem?sponsorshipToken=" + encodeURIComponent(sponsorshipToken),
|
|
request,
|
|
true,
|
|
false,
|
|
);
|
|
}
|
|
|
|
async postResendSponsorshipOffer(sponsoringOrgId: string): Promise<void> {
|
|
return await this.send(
|
|
"POST",
|
|
"/organization/sponsorship/" + sponsoringOrgId + "/families-for-enterprise/resend",
|
|
null,
|
|
true,
|
|
false,
|
|
);
|
|
}
|
|
|
|
protected async doAuthRefresh(): Promise<void> {
|
|
const refreshToken = await this.tokenService.getRefreshToken();
|
|
if (refreshToken != null && refreshToken !== "") {
|
|
return this.doRefreshToken();
|
|
}
|
|
|
|
const clientId = await this.tokenService.getClientId();
|
|
const clientSecret = await this.tokenService.getClientSecret();
|
|
if (!Utils.isNullOrWhitespace(clientId) && !Utils.isNullOrWhitespace(clientSecret)) {
|
|
return this.doApiTokenRefresh();
|
|
}
|
|
|
|
throw new Error("Cannot refresh token, no refresh token or api keys are stored");
|
|
}
|
|
|
|
protected async doRefreshToken(): Promise<void> {
|
|
const refreshToken = await this.tokenService.getRefreshToken();
|
|
if (refreshToken == null || refreshToken === "") {
|
|
throw new Error();
|
|
}
|
|
const headers = new Headers({
|
|
"Content-Type": "application/x-www-form-urlencoded; charset=utf-8",
|
|
Accept: "application/json",
|
|
"Device-Type": this.deviceType,
|
|
});
|
|
if (this.customUserAgent != null) {
|
|
headers.set("User-Agent", this.customUserAgent);
|
|
}
|
|
|
|
const decodedToken = await this.tokenService.decodeAccessToken();
|
|
const response = await this.fetch(
|
|
new Request(this.environmentService.getIdentityUrl() + "/connect/token", {
|
|
body: this.qsStringify({
|
|
grant_type: "refresh_token",
|
|
client_id: decodedToken.client_id,
|
|
refresh_token: refreshToken,
|
|
}),
|
|
cache: "no-store",
|
|
credentials: this.getCredentials(),
|
|
headers: headers,
|
|
method: "POST",
|
|
}),
|
|
);
|
|
|
|
if (response.status === 200) {
|
|
const responseJson = await response.json();
|
|
const tokenResponse = new IdentityTokenResponse(responseJson);
|
|
|
|
const vaultTimeoutAction = await this.stateService.getVaultTimeoutAction();
|
|
const vaultTimeout = await this.stateService.getVaultTimeout();
|
|
|
|
await this.tokenService.setTokens(
|
|
tokenResponse.accessToken,
|
|
tokenResponse.refreshToken,
|
|
vaultTimeoutAction as VaultTimeoutAction,
|
|
vaultTimeout,
|
|
);
|
|
} else {
|
|
const error = await this.handleError(response, true, true);
|
|
return Promise.reject(error);
|
|
}
|
|
}
|
|
|
|
protected async doApiTokenRefresh(): Promise<void> {
|
|
const clientId = await this.tokenService.getClientId();
|
|
const clientSecret = await this.tokenService.getClientSecret();
|
|
|
|
const appId = await this.appIdService.getAppId();
|
|
const deviceRequest = new DeviceRequest(appId, this.platformUtilsService);
|
|
const tokenRequest = new UserApiTokenRequest(
|
|
clientId,
|
|
clientSecret,
|
|
new TokenTwoFactorRequest(),
|
|
deviceRequest,
|
|
);
|
|
|
|
const response = await this.postIdentityToken(tokenRequest);
|
|
if (!(response instanceof IdentityTokenResponse)) {
|
|
throw new Error("Invalid response received when refreshing api token");
|
|
}
|
|
|
|
const vaultTimeoutAction = await this.stateService.getVaultTimeoutAction();
|
|
const vaultTimeout = await this.stateService.getVaultTimeout();
|
|
|
|
await this.tokenService.setAccessToken(
|
|
response.accessToken,
|
|
vaultTimeoutAction as VaultTimeoutAction,
|
|
vaultTimeout,
|
|
);
|
|
}
|
|
|
|
async send(
|
|
method: "GET" | "POST" | "PUT" | "DELETE",
|
|
path: string,
|
|
body: any,
|
|
authed: boolean,
|
|
hasResponse: boolean,
|
|
apiUrl?: string,
|
|
alterHeaders?: (headers: Headers) => void,
|
|
): Promise<any> {
|
|
apiUrl = Utils.isNullOrWhitespace(apiUrl) ? this.environmentService.getApiUrl() : apiUrl;
|
|
|
|
// Prevent directory traversal from malicious paths
|
|
const pathParts = path.split("?");
|
|
const requestUrl =
|
|
apiUrl + Utils.normalizePath(pathParts[0]) + (pathParts.length > 1 ? `?${pathParts[1]}` : "");
|
|
|
|
const headers = new Headers({
|
|
"Device-Type": this.deviceType,
|
|
});
|
|
if (this.customUserAgent != null) {
|
|
headers.set("User-Agent", this.customUserAgent);
|
|
}
|
|
|
|
const requestInit: RequestInit = {
|
|
cache: "no-store",
|
|
credentials: this.getCredentials(),
|
|
method: method,
|
|
};
|
|
|
|
if (authed) {
|
|
const authHeader = await this.getActiveBearerToken();
|
|
headers.set("Authorization", "Bearer " + authHeader);
|
|
}
|
|
if (body != null) {
|
|
if (typeof body === "string") {
|
|
requestInit.body = body;
|
|
headers.set("Content-Type", "application/x-www-form-urlencoded; charset=utf-8");
|
|
} else if (typeof body === "object") {
|
|
if (body instanceof FormData) {
|
|
requestInit.body = body;
|
|
} else {
|
|
headers.set("Content-Type", "application/json; charset=utf-8");
|
|
requestInit.body = JSON.stringify(body);
|
|
}
|
|
}
|
|
}
|
|
if (hasResponse) {
|
|
headers.set("Accept", "application/json");
|
|
}
|
|
if (alterHeaders != null) {
|
|
alterHeaders(headers);
|
|
}
|
|
|
|
requestInit.headers = headers;
|
|
const response = await this.fetch(new Request(requestUrl, requestInit));
|
|
|
|
const responseType = response.headers.get("content-type");
|
|
const responseIsJson = responseType != null && responseType.indexOf("application/json") !== -1;
|
|
if (hasResponse && response.status === 200 && responseIsJson) {
|
|
const responseJson = await response.json();
|
|
return responseJson;
|
|
} else if (response.status !== 200) {
|
|
const error = await this.handleError(response, false, authed);
|
|
return Promise.reject(error);
|
|
}
|
|
}
|
|
|
|
private async handleError(
|
|
response: Response,
|
|
tokenError: boolean,
|
|
authed: boolean,
|
|
): Promise<ErrorResponse> {
|
|
let responseJson: any = null;
|
|
if (this.isJsonResponse(response)) {
|
|
responseJson = await response.json();
|
|
} else if (this.isTextResponse(response)) {
|
|
responseJson = { Message: await response.text() };
|
|
}
|
|
|
|
if (authed) {
|
|
if (
|
|
response.status === 401 ||
|
|
response.status === 403 ||
|
|
(tokenError &&
|
|
response.status === 400 &&
|
|
responseJson != null &&
|
|
responseJson.error === "invalid_grant")
|
|
) {
|
|
await this.logoutCallback(true);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
return new ErrorResponse(responseJson, response.status, tokenError);
|
|
}
|
|
|
|
private qsStringify(params: any): string {
|
|
return Object.keys(params)
|
|
.map((key) => {
|
|
return encodeURIComponent(key) + "=" + encodeURIComponent(params[key]);
|
|
})
|
|
.join("&");
|
|
}
|
|
|
|
private getCredentials(): RequestCredentials {
|
|
if (!this.isWebClient || this.environmentService.hasBaseUrl()) {
|
|
return "include";
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
private addEventParameters(base: string, start: string, end: string, token: string) {
|
|
if (start != null) {
|
|
base += "?start=" + start;
|
|
}
|
|
if (end != null) {
|
|
base += base.indexOf("?") > -1 ? "&" : "?";
|
|
base += "end=" + end;
|
|
}
|
|
if (token != null) {
|
|
base += base.indexOf("?") > -1 ? "&" : "?";
|
|
base += "continuationToken=" + token;
|
|
}
|
|
return base;
|
|
}
|
|
|
|
private isJsonResponse(response: Response): boolean {
|
|
const typeHeader = response.headers.get("content-type");
|
|
return typeHeader != null && typeHeader.indexOf("application/json") > -1;
|
|
}
|
|
|
|
private isTextResponse(response: Response): boolean {
|
|
const typeHeader = response.headers.get("content-type");
|
|
return typeHeader != null && typeHeader.indexOf("text") > -1;
|
|
}
|
|
}
|