mirror of
https://github.com/bitwarden/browser
synced 2026-02-08 12:40:26 +00:00
PM-20532 - SendTokenService - add error types + type guards.
This commit is contained in:
@@ -1,6 +1,9 @@
|
||||
import { SendHashedPassword } from "../../../key-management/sends/send-password.service";
|
||||
import { SendAccessToken } from "../models/send-access-token";
|
||||
import { TryGetSendAccessTokenError } from "../services/send-token.service";
|
||||
import {
|
||||
GetSendAcccessTokenError,
|
||||
TryGetSendAccessTokenError,
|
||||
} from "../services/send-token.service";
|
||||
|
||||
export type SendAccessCredentialsType = "password" | "email-otp";
|
||||
|
||||
@@ -15,21 +18,7 @@ export type SendEmailOtpCredentials = {
|
||||
};
|
||||
export type SendAccessCredentials = SendPasswordCredentials | SendEmailOtpCredentials;
|
||||
|
||||
// TODO: add JSdocs
|
||||
export abstract class SendTokenService {
|
||||
// SendAccessTokens need to be stored in session storage once retrieved.
|
||||
// All SendAccessTokens are scoped to a specific send id so all getting and setting should accept a send id.
|
||||
|
||||
// TODO: should this abstraction have separate methods for requesting an access token from the server
|
||||
// and for getting the access token from storage?
|
||||
// One method that does both is ideal.
|
||||
// We will need to extend inputs to include the send id and the credentials.
|
||||
// We will also need to store the send access token with it's expires_in value so we know if it's expired
|
||||
// so that we don't hand out an expired token to make a request.
|
||||
|
||||
// Returned error types should be discriminated union with a type that can be conditioned off for logic.
|
||||
|
||||
// TODO: define return types.
|
||||
// TODO: consider converting to observable.
|
||||
/**
|
||||
* Attempts to retrieve a SendAccessToken for the given sendId.
|
||||
@@ -46,10 +35,18 @@ export abstract class SendTokenService {
|
||||
sendId: string,
|
||||
) => Promise<SendAccessToken | TryGetSendAccessTokenError>;
|
||||
|
||||
abstract getSendAccessTokenWithCredentials: (
|
||||
/**
|
||||
* Retrieves a SendAccessToken for the given sendId using the provided credentials.
|
||||
* If the access token is successfully retrieved from the server, it stores the token in session storage and returns it.
|
||||
* If the access token cannot be granted due to invalid credentials, it returns a GetSendAcccessTokenError.
|
||||
* @param sendId The ID of the send to retrieve the access token for.
|
||||
* @param sendAccessCredentials The credentials to use for accessing the send.
|
||||
* @returns A promise that resolves to a SendAccessToken if found and valid, or a GetSendAcccessTokenError if not.
|
||||
*/
|
||||
abstract getSendAccessToken: (
|
||||
sendId: string,
|
||||
sendAccessCredentials: SendAccessCredentials,
|
||||
) => Promise<void>;
|
||||
) => Promise<SendAccessToken | GetSendAcccessTokenError>;
|
||||
|
||||
/**
|
||||
* Hashes a password for send access.
|
||||
|
||||
@@ -6,10 +6,12 @@ import { SendAccessTokenRequest } from "../../models/request/identity-token/send
|
||||
import { SendTokenApiService as SendTokenApiServiceAbstraction } from "../abstractions/send-token-api.service";
|
||||
import { SendAccessToken } from "../models/send-access-token";
|
||||
|
||||
export type SendTokenApiRetrievalError = "password-required" | "otp-required" | "unknown-error";
|
||||
|
||||
// Consider adding types for submission with credentials:
|
||||
// | "invalid-password"
|
||||
export type SendTokenApiRetrievalError =
|
||||
| "password-required"
|
||||
| "otp-required"
|
||||
| "invalid-password"
|
||||
| "invalid-otp"
|
||||
| "unknown-error";
|
||||
|
||||
export class SendTokenApiService implements SendTokenApiServiceAbstraction {
|
||||
constructor(
|
||||
|
||||
@@ -22,7 +22,6 @@ import { SendTokenApiRetrievalError, SendTokenApiService } from "./send-token-ap
|
||||
|
||||
// TODO: add JSDocs
|
||||
// TODO: add tests for this service.
|
||||
|
||||
export const SEND_ACCESS_TOKEN_DICT = KeyDefinition.record<SendAccessToken, string>(
|
||||
SEND_ACCESS_DISK,
|
||||
"accessTokenDict",
|
||||
@@ -33,9 +32,29 @@ export const SEND_ACCESS_TOKEN_DICT = KeyDefinition.record<SendAccessToken, stri
|
||||
},
|
||||
);
|
||||
|
||||
// TODO: add different error types for each method.
|
||||
export type TryGetSendAccessTokenError = "expired" | SendTokenApiRetrievalError;
|
||||
// export type GetSendAccessTokenWithCredsError = <subset of SendTokenApiRetrievalError>;
|
||||
type CredentialsRequiredApiError = Extract<
|
||||
SendTokenApiRetrievalError,
|
||||
"password-required" | "otp-required" | "unknown-error"
|
||||
>;
|
||||
|
||||
function isCredentialsRequiredApiError(
|
||||
error: SendTokenApiRetrievalError,
|
||||
): error is CredentialsRequiredApiError {
|
||||
return error === "password-required" || error === "otp-required" || error === "unknown-error";
|
||||
}
|
||||
|
||||
export type TryGetSendAccessTokenError = "expired" | CredentialsRequiredApiError;
|
||||
|
||||
export type GetSendAcccessTokenError = Extract<
|
||||
SendTokenApiRetrievalError,
|
||||
"invalid-password" | "invalid-otp" | "unknown-error"
|
||||
>;
|
||||
|
||||
function isGetSendAccessTokenError(
|
||||
error: SendTokenApiRetrievalError,
|
||||
): error is GetSendAcccessTokenError {
|
||||
return error === "invalid-password" || error === "invalid-otp" || error === "unknown-error";
|
||||
}
|
||||
|
||||
export class SendTokenService implements SendTokenServiceAbstraction {
|
||||
private sendAccessTokenDictGlobalState: GlobalState<Record<string, SendAccessToken>> | undefined;
|
||||
@@ -82,13 +101,20 @@ export class SendTokenService implements SendTokenServiceAbstraction {
|
||||
return result;
|
||||
}
|
||||
|
||||
return result;
|
||||
if (isCredentialsRequiredApiError(result)) {
|
||||
// If we get an expected API error, we return it.
|
||||
// Typically, this will be a "password-required" or "otp-required" error to communicate that the send requires credentials to access.
|
||||
return result;
|
||||
}
|
||||
|
||||
// If we get an unexpected error, we throw.
|
||||
throw new Error(`Unexpected and unhandled API error retrieving send access token: ${result}`);
|
||||
}
|
||||
|
||||
async getSendAccessTokenWithCredentials(
|
||||
async getSendAccessToken(
|
||||
sendId: string,
|
||||
sendCredentials: SendAccessCredentials,
|
||||
): Promise<void> {
|
||||
): Promise<SendAccessToken | GetSendAcccessTokenError> {
|
||||
// Validate the sendId
|
||||
this.validateSendId(sendId);
|
||||
|
||||
@@ -104,11 +130,17 @@ export class SendTokenService implements SendTokenServiceAbstraction {
|
||||
if (result instanceof SendAccessToken) {
|
||||
// If we get a token back, we need to store it in the global state.
|
||||
await this.setSendAccessTokenInStorage(sendId, result);
|
||||
return;
|
||||
return result;
|
||||
}
|
||||
|
||||
// Handle errors from the API service.
|
||||
// return result;
|
||||
if (isGetSendAccessTokenError(result)) {
|
||||
// If we get an expected API error, we return it.
|
||||
// Typically, this will be due to an invalid credentials error
|
||||
return result;
|
||||
}
|
||||
|
||||
// If we get an unexpected error, we throw.
|
||||
throw new Error(`Unexpected and unhandled API error retrieving send access token: ${result}`);
|
||||
}
|
||||
|
||||
async hashPassword(password: string, keyMaterialUrlB64: string): Promise<SendHashedPassword> {
|
||||
|
||||
Reference in New Issue
Block a user