1
0
mirror of https://github.com/bitwarden/browser synced 2026-02-08 20:50:28 +00:00

PM-20532 - WIP on send access token layers.

This commit is contained in:
Jared Snider
2025-05-23 15:57:09 -04:00
parent b55fc3110f
commit 5211d651ac
4 changed files with 103 additions and 10 deletions

View File

@@ -1,4 +1,6 @@
import { SendAccessTokenRequest } from "../../models/request/identity-token/send-access-token.request";
import { SendAccessToken } from "../models/send-access-token";
import { SendTokenApiRetrievalError } from "../services/send-token-api.service";
/**
* Abstract class for the SendTokenApiService.
@@ -13,5 +15,7 @@ export abstract class SendTokenApiService {
// ExpiredRequiredPassword // these will live at higher level in SendTokenService
// ExpiredRequiredEmailOtp
abstract requestSendAccessToken: (request: SendAccessTokenRequest) => Promise<unknown>;
abstract requestSendAccessToken: (
request: SendAccessTokenRequest,
) => Promise<SendAccessToken | SendTokenApiRetrievalError>;
}

View File

@@ -1,3 +1,6 @@
import { SendAccessToken } from "../models/send-access-token";
import { SendTokenRetrievalError } from "../services/send-token.service";
export type SendAccessCredentialsType = "password" | "email-otp";
export type SendPasswordCredentials = {
@@ -26,7 +29,9 @@ export abstract class SendTokenService {
// TODO: define return types.
// TODO: consider converting to observable.
abstract tryGetSendAccessToken: (sendId: string) => Promise<void>;
abstract tryGetSendAccessToken: (
sendId: string,
) => Promise<SendAccessToken | SendTokenRetrievalError>;
abstract getSendAccessTokenWithCredentials: (
sendId: string,

View File

@@ -4,6 +4,9 @@ import { ApiService } from "../../../abstractions/api.service";
import { EnvironmentService } from "../../../platform/abstractions/environment.service";
import { SendAccessTokenRequest } from "../../models/request/identity-token/send-access-token.request";
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";
export class SendTokenApiService implements SendTokenApiServiceAbstraction {
constructor(
@@ -11,7 +14,9 @@ export class SendTokenApiService implements SendTokenApiServiceAbstraction {
private apiService: ApiService,
) {}
async requestSendAccessToken(request: SendAccessTokenRequest): Promise<void> {
async requestSendAccessToken(
request: SendAccessTokenRequest,
): Promise<SendAccessToken | SendTokenApiRetrievalError> {
const payload = request.toIdentityTokenPayload();
const headers = new Headers({
@@ -31,8 +36,17 @@ export class SendTokenApiService implements SendTokenApiServiceAbstraction {
cache: "no-store",
});
await this.apiService.fetch(req);
const response = await this.apiService.fetch(req);
const responseJson = await response.json();
// TODO: add result processing
if (response.status === 200) {
const sendAccessToken = SendAccessToken.fromJson(responseJson);
return sendAccessToken;
} else if (response.status === 400) {
// TODO: add correct error handling for 400
return "password-required";
}
return "unknown-error";
}
}

View File

@@ -1,14 +1,20 @@
import { firstValueFrom } from "rxjs";
import { Jsonify } from "type-fest";
import { GlobalStateProvider, KeyDefinition, SEND_ACCESS_DISK } from "../../../platform/state";
// import { SendAccessTokenRequest } from "../../models/request/identity-token/send-access-token.request";
import {
GlobalState,
GlobalStateProvider,
KeyDefinition,
SEND_ACCESS_DISK,
} from "../../../platform/state";
import { SendAccessTokenRequest } from "../../models/request/identity-token/send-access-token.request";
import {
SendAccessCredentials,
SendTokenService as SendTokenServiceAbstraction,
} from "../abstractions/send-token.service";
import { SendAccessToken } from "../models/send-access-token";
import { SendTokenApiService } from "./send-token-api.service";
import { SendTokenApiRetrievalError, SendTokenApiService } from "./send-token-api.service";
export const SEND_ACCESS_TOKEN_DICT = KeyDefinition.record<SendAccessToken, string>(
SEND_ACCESS_DISK,
@@ -20,13 +26,53 @@ export const SEND_ACCESS_TOKEN_DICT = KeyDefinition.record<SendAccessToken, stri
},
);
export type SendTokenRetrievalError = "expired" | SendTokenApiRetrievalError;
export class SendTokenService implements SendTokenServiceAbstraction {
private sendAccessTokenDictGlobalState: GlobalState<Record<string, SendAccessToken>> | undefined;
constructor(
private globalStateProvider: GlobalStateProvider,
private sendTokenApiService: SendTokenApiService,
) {}
) {
this.initializeState();
}
async getSendAccessToken(
private initializeState(): void {
this.sendAccessTokenDictGlobalState = this.globalStateProvider.get(SEND_ACCESS_TOKEN_DICT);
}
async tryGetSendAccessToken(sendId: string): Promise<SendAccessToken | SendTokenRetrievalError> {
// TODO: check in storage for the access token and if it is expired.
const sendAccessTokenFromStorage = await this.getSendAccessTokenFromStorage(sendId);
if (sendAccessTokenFromStorage != null) {
// If it is expired, we return expired token error.
if (sendAccessTokenFromStorage.isExpired()) {
return "expired";
} else {
// If it is not expired, we return
return sendAccessTokenFromStorage;
}
}
// If we don't have a token in storage, we can try to request a new token from the server.
const request = new SendAccessTokenRequest(sendId);
// try {
const result = await this.sendTokenApiService.requestSendAccessToken(request);
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 result;
}
return result;
}
async getSendAccessTokenWithCredentials(
sendId: string,
sendCredentials: SendAccessCredentials | undefined,
): Promise<void> {
@@ -36,4 +82,28 @@ export class SendTokenService implements SendTokenServiceAbstraction {
// const request = new SendAccessTokenRequest(sendId, sendCredentials);
// const result = await this.sendTokenApiService.requestSendAccessToken(request);
}
private async getSendAccessTokenFromStorage(
sendId: string,
): Promise<SendAccessToken | undefined> {
if (this.sendAccessTokenDictGlobalState != null) {
const sendAccessTokenDict = await firstValueFrom(this.sendAccessTokenDictGlobalState.state$);
return sendAccessTokenDict?.[sendId];
}
return undefined;
}
private async setSendAccessTokenInStorage(
sendId: string,
sendAccessToken: SendAccessToken,
): Promise<void> {
if (this.sendAccessTokenDictGlobalState != null) {
await this.sendAccessTokenDictGlobalState.update((sendAccessTokenDict) => {
sendAccessTokenDict ??= {}; // Initialize if undefined
sendAccessTokenDict[sendId] = sendAccessToken;
return sendAccessTokenDict;
});
}
}
}