1
0
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:
Jared Snider
2025-05-29 12:17:01 -04:00
parent a3ecc62907
commit 226af6cf79
3 changed files with 62 additions and 31 deletions

View File

@@ -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.

View File

@@ -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(

View File

@@ -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> {