Add StateVersion.Three to fix premium migration (#666)

* Add StateVersion.Three to fix premium migration
This commit is contained in:
Thomas Rittson 2022-02-11 13:40:13 +10:00 committed by GitHub
parent 52f77c0277
commit 5fad7c666f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 48 additions and 13 deletions

View File

@ -1,5 +1,6 @@
export enum StateVersion { export enum StateVersion {
One = 1, // Original flat key/value pair store One = 1, // Original flat key/value pair store
Two = 2, // Move to a typed State object Two = 2, // Move to a typed State object
Latest = Two, Three = 3, // Fix migration of users' premium status
Latest = Three,
} }

View File

@ -24,6 +24,8 @@ import { GlobalStateFactory } from "../factories/globalStateFactory";
import { StateFactory } from "../factories/stateFactory"; import { StateFactory } from "../factories/stateFactory";
import { Account, AccountSettings } from "../models/domain/account"; import { Account, AccountSettings } from "../models/domain/account";
import { TokenService } from "./token.service";
// Originally (before January 2022) storage was handled as a flat key/value pair store. // Originally (before January 2022) storage was handled as a flat key/value pair store.
// With the move to a typed object for state storage these keys should no longer be in use anywhere outside of this migration. // With the move to a typed object for state storage these keys should no longer be in use anywhere outside of this migration.
const v1Keys: { [key: string]: string } = { const v1Keys: { [key: string]: string } = {
@ -153,6 +155,9 @@ export class StateMigrationService<
case StateVersion.One: case StateVersion.One:
await this.migrateStateFrom1To2(); await this.migrateStateFrom1To2();
break; break;
case StateVersion.Two:
await this.migrateStateFrom2To3();
break;
} }
currentStateVersion += 1; currentStateVersion += 1;
@ -448,6 +453,27 @@ export class StateMigrationService<
} }
} }
protected async migrateStateFrom2To3(): Promise<void> {
const authenticatedUserIds = await this.get<string[]>(keys.authenticatedAccounts);
await Promise.all(
authenticatedUserIds.map(async (userId) => {
const account = await this.get<TAccount>(userId);
if (
account?.profile?.hasPremiumPersonally === null &&
account.tokens?.accessToken != null
) {
const decodedToken = await TokenService.decodeToken(account.tokens.accessToken);
account.profile.hasPremiumPersonally = decodedToken.premium;
await this.set(userId, account);
}
})
);
const globals = await this.getGlobals();
globals.stateVersion = StateVersion.Three;
await this.set(keys.global, globals);
}
protected get options(): StorageOptions { protected get options(): StorageOptions {
return { htmlStorageLocation: HtmlStorageLocation.Local }; return { htmlStorageLocation: HtmlStorageLocation.Local };
} }

View File

@ -6,6 +6,25 @@ import { Utils } from "../misc/utils";
import { IdentityTokenResponse } from "../models/response/identityTokenResponse"; import { IdentityTokenResponse } from "../models/response/identityTokenResponse";
export class TokenService implements TokenServiceAbstraction { export class TokenService implements TokenServiceAbstraction {
static decodeToken(token: string): Promise<any> {
if (token == null) {
throw new Error("Token not provided.");
}
const parts = token.split(".");
if (parts.length !== 3) {
throw new Error("JWT must have 3 parts");
}
const decoded = Utils.fromUrlB64ToUtf8(parts[1]);
if (decoded == null) {
throw new Error("Cannot decode the token");
}
const decodedToken = JSON.parse(decoded);
return decodedToken;
}
constructor(private stateService: StateService) {} constructor(private stateService: StateService) {}
async setTokens( async setTokens(
@ -87,18 +106,7 @@ export class TokenService implements TokenServiceAbstraction {
throw new Error("Token not found."); throw new Error("Token not found.");
} }
const parts = token.split("."); return TokenService.decodeToken(token);
if (parts.length !== 3) {
throw new Error("JWT must have 3 parts");
}
const decoded = Utils.fromUrlB64ToUtf8(parts[1]);
if (decoded == null) {
throw new Error("Cannot decode the token");
}
const decodedToken = JSON.parse(decoded);
return decodedToken;
} }
async getTokenExpirationDate(): Promise<Date> { async getTokenExpirationDate(): Promise<Date> {