Skip to content

Commit 274838b

Browse files
committed
v2 token re-validation on 401
1 parent 9479d37 commit 274838b

File tree

9 files changed

+130
-45
lines changed

9 files changed

+130
-45
lines changed

packages/blockchain-sdk-solana/src/auth/v2/sign-message/solana-sign-message-v2-authentication-facade-factory.ts

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@ import {
22
AuthenticationFacade,
33
AuthenticationFacadeFactory,
44
Authenticator,
5+
AuthV2Api,
56
Ed25519TokenBodyParser,
67
TokenParser,
78
TokenSigner,
8-
AuthV2Api,
99
} from '@dialectlabs/sdk';
1010

1111
import { SolanaSignMessageV2TokenGenerator } from './solana-sign-message-v2-token-generator';
@@ -14,26 +14,25 @@ import { SolanaV2TokenValidator } from '../solana-v2-token-validator';
1414
export class SolanaSignMessageV2AuthenticationFacadeFactory extends AuthenticationFacadeFactory {
1515
constructor(
1616
private readonly tokenSigner: TokenSigner,
17-
private readonly baseUrl: string,
17+
private readonly api: AuthV2Api,
1818
) {
1919
super();
2020
}
2121

22-
static createAuthenticator(): Authenticator {
22+
static createAuthenticator(api: AuthV2Api): Authenticator {
2323
return new Authenticator(
2424
new TokenParser(new Ed25519TokenBodyParser()),
25-
new SolanaV2TokenValidator(),
25+
new SolanaV2TokenValidator(api),
2626
);
2727
}
2828

2929
get(): AuthenticationFacade {
3030
return new AuthenticationFacade(
3131
this.tokenSigner,
32-
new SolanaSignMessageV2TokenGenerator(
33-
this.tokenSigner,
34-
new AuthV2Api(this.baseUrl),
32+
new SolanaSignMessageV2TokenGenerator(this.tokenSigner, this.api),
33+
SolanaSignMessageV2AuthenticationFacadeFactory.createAuthenticator(
34+
this.api,
3535
),
36-
SolanaSignMessageV2AuthenticationFacadeFactory.createAuthenticator(),
3736
);
3837
}
3938
}

packages/blockchain-sdk-solana/src/auth/v2/sign-message/solana-sign-message-v2-token-generator.ts

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,19 @@ import {
55
type Token,
66
TokenGenerator,
77
type TokenSigner,
8+
withApiErrorParsing,
89
} from '@dialectlabs/sdk';
910
import bs58 from 'bs58';
11+
import { v2tokenValidationCache } from '../solana-v2-token-validator';
1012

1113
export class SolanaSignMessageV2TokenGenerator extends TokenGenerator {
1214
constructor(signer: TokenSigner, private readonly api: AuthV2Api) {
1315
super(signer);
1416
}
1517

1618
override async generate(): Promise<Token> {
17-
const prepareResponse = await this.api.prepareSolanaAuth(
18-
this.signer.subject,
19+
const prepareResponse = await withApiErrorParsing(
20+
this.api.prepareSolanaAuth(this.signer.subject),
1921
);
2022

2123
const messageToSign = prepareResponse.message;
@@ -24,9 +26,8 @@ export class SolanaSignMessageV2TokenGenerator extends TokenGenerator {
2426
new TextEncoder().encode(messageToSign),
2527
);
2628

27-
const tokenResponse = await this.api.verifySolanaAuth(
28-
messageToSign,
29-
bs58.encode(signature),
29+
const tokenResponse = await withApiErrorParsing(
30+
this.api.verifySolanaAuth(messageToSign, bs58.encode(signature)),
3031
);
3132

3233
const tokenStr = tokenResponse.token;
@@ -36,6 +37,11 @@ export class SolanaSignMessageV2TokenGenerator extends TokenGenerator {
3637
throw new Error('Invalid token format from backend');
3738
}
3839

40+
v2tokenValidationCache[tokenStr] = {
41+
lastValidatedAt: new Date(),
42+
isValid: true,
43+
};
44+
3945
return {
4046
rawValue: tokenStr,
4147
header: jsonParseFromBase64(base64Header),

packages/blockchain-sdk-solana/src/auth/v2/sign-tx/solana-sign-tx-v2-authentication-facade-factory.ts

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,26 +14,23 @@ import type { SolanaEd25519TokenSigner } from '../../ed25519/solana-ed25519-toke
1414
export class SolanaSignTxV2AuthenticationFacadeFactory extends AuthenticationFacadeFactory {
1515
constructor(
1616
private readonly tokenSigner: SolanaEd25519TokenSigner,
17-
private readonly baseUrl: string,
17+
private readonly api: AuthV2Api,
1818
) {
1919
super();
2020
}
2121

22-
static createAuthenticator(): Authenticator {
22+
static createAuthenticator(api: AuthV2Api): Authenticator {
2323
return new Authenticator(
2424
new TokenParser(new Ed25519TokenBodyParser()),
25-
new SolanaV2TokenValidator(),
25+
new SolanaV2TokenValidator(api),
2626
);
2727
}
2828

2929
get(): AuthenticationFacade {
3030
return new AuthenticationFacade(
3131
this.tokenSigner,
32-
new SolanaSignTxV2TokenGenerator(
33-
this.tokenSigner,
34-
new AuthV2Api(this.baseUrl),
35-
),
36-
SolanaSignTxV2AuthenticationFacadeFactory.createAuthenticator(),
32+
new SolanaSignTxV2TokenGenerator(this.tokenSigner, this.api),
33+
SolanaSignTxV2AuthenticationFacadeFactory.createAuthenticator(this.api),
3734
);
3835
}
3936
}

packages/blockchain-sdk-solana/src/auth/v2/sign-tx/solana-sign-tx-v2-token-generator.ts

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,22 @@
11
import {
2+
type AuthV2Api,
23
bytesFromBase64,
34
jsonParseFromBase64,
4-
type AuthV2Api,
55
type Token,
66
TokenGenerator,
77
type TokenSigner,
8+
withApiErrorParsing,
89
} from '@dialectlabs/sdk';
10+
import { v2tokenValidationCache } from '../solana-v2-token-validator';
911

1012
export class SolanaSignTxV2TokenGenerator extends TokenGenerator {
1113
constructor(signer: TokenSigner, private readonly api: AuthV2Api) {
1214
super(signer);
1315
}
1416

1517
override async generate(): Promise<Token> {
16-
const prepareResponse = await this.api.prepareSolanaTxAuth(
17-
this.signer.subject,
18+
const prepareResponse = await withApiErrorParsing(
19+
this.api.prepareSolanaTxAuth(this.signer.subject),
1820
);
1921

2022
const transactionBase64 = prepareResponse.transaction;
@@ -23,8 +25,8 @@ export class SolanaSignTxV2TokenGenerator extends TokenGenerator {
2325
Buffer.from(transactionBase64, 'base64'),
2426
);
2527

26-
const tokenResponse = await this.api.verifySolanaTxAuth(
27-
Buffer.from(signature).toString('base64'),
28+
const tokenResponse = await withApiErrorParsing(
29+
this.api.verifySolanaTxAuth(Buffer.from(signature).toString('base64')),
2830
);
2931

3032
const tokenStr = tokenResponse.token;
@@ -34,6 +36,11 @@ export class SolanaSignTxV2TokenGenerator extends TokenGenerator {
3436
throw new Error('Invalid token format from backend');
3537
}
3638

39+
v2tokenValidationCache[tokenStr] = {
40+
lastValidatedAt: new Date(),
41+
isValid: true,
42+
};
43+
3744
return {
3845
rawValue: tokenStr,
3946
header: jsonParseFromBase64(base64Header),
Lines changed: 71 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,82 @@
1-
import type { TokenHeader } from '@dialectlabs/sdk';
2-
import { TokenValidator } from '@dialectlabs/sdk';
1+
import type { AuthV2Api, Token, TokenHeader } from '@dialectlabs/sdk';
2+
import {
3+
AuthenticationError,
4+
TokenValidator,
5+
withApiErrorParsing,
6+
} from '@dialectlabs/sdk';
7+
8+
export const v2tokenValidationCache: Record<
9+
string,
10+
{
11+
lastValidatedAt: Date;
12+
isValid: boolean;
13+
}
14+
> = {};
15+
16+
const VALIDATION_CACHE_TTL = 1000 * 60; // 1 minute
17+
18+
const validationPromises: Record<string, Promise<void>> = {};
319

420
export class SolanaV2TokenValidator extends TokenValidator {
5-
constructor() {
21+
constructor(private readonly api: AuthV2Api) {
622
super();
723
}
824

925
override canValidate(tokenHeader: TokenHeader): boolean {
1026
return tokenHeader.typ === 'JWT' && tokenHeader.alg === 'HS256';
1127
}
1228

13-
override isSignatureValid(): boolean {
14-
return true;
29+
override isSignatureValid(token: Token): boolean {
30+
const cachedValidation = v2tokenValidationCache[token.rawValue];
31+
const shouldRevalidate =
32+
!cachedValidation ||
33+
!cachedValidation.isValid ||
34+
cachedValidation.lastValidatedAt.getTime() + VALIDATION_CACHE_TTL <
35+
Date.now();
36+
37+
if (shouldRevalidate) {
38+
this.validateAndCache(token).catch(console.error);
39+
}
40+
41+
if (!cachedValidation) {
42+
return true;
43+
}
44+
if (!cachedValidation.isValid) {
45+
return false;
46+
}
47+
return cachedValidation.isValid;
48+
}
49+
50+
private async validateAndCache(token: Token) {
51+
if (validationPromises[token.rawValue]) {
52+
return validationPromises[token.rawValue];
53+
}
54+
55+
const now = new Date();
56+
57+
validationPromises[token.rawValue] = new Promise(async (resolve) => {
58+
try {
59+
await withApiErrorParsing(this.api.getAuth(token.rawValue));
60+
v2tokenValidationCache[token.rawValue] = {
61+
lastValidatedAt: now,
62+
isValid: true,
63+
};
64+
resolve();
65+
} catch (e) {
66+
if (e instanceof AuthenticationError) {
67+
v2tokenValidationCache[token.rawValue] = {
68+
lastValidatedAt: now,
69+
isValid: false,
70+
};
71+
} else {
72+
console.error(`Failed to validate token: ${JSON.stringify(e)}`);
73+
}
74+
resolve();
75+
} finally {
76+
delete validationPromises[token.rawValue];
77+
}
78+
});
79+
80+
return validationPromises[token.rawValue];
1581
}
1682
}

packages/blockchain-sdk-solana/src/sdk/sdk.ts

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
import { DialectSolanaWalletAdapterWrapper } from '../wallet-adapter/dialect-solana-wallet-adapter-wrapper';
2-
import type {
2+
import {
3+
AuthV2Api,
34
BlockchainSdk,
45
BlockchainSdkFactory,
56
Config,
7+
EncryptionKeysProvider,
68
Environment,
9+
IllegalArgumentError,
710
} from '@dialectlabs/sdk';
8-
import { EncryptionKeysProvider, IllegalArgumentError } from '@dialectlabs/sdk';
911
import type { DialectSolanaWalletAdapter } from '../wallet-adapter/dialect-solana-wallet-adapter.interface';
1012
import { DialectSolanaWalletAdapterEncryptionKeysProvider } from '../encryption/encryption-keys-provider';
1113
import { SolanaEd25519AuthenticationFacadeFactory } from '../auth/ed25519/solana-ed25519-authentication-facade-factory';
@@ -100,21 +102,27 @@ Solana settings:
100102
);
101103
}
102104
}
103-
if (config.dialectCloud.apiVersion === 2) {
105+
if (
106+
config.dialectCloud.apiVersion === 2 ||
107+
config.dialectCloud.apiVersion === '2_compat'
108+
) {
109+
const v2AuthApi = new AuthV2Api(config.dialectCloud.v2Url);
104110
if (solanaConfig.wallet.canSignMessage()) {
105111
return new SolanaSignMessageV2AuthenticationFacadeFactory(
106112
new DialectWalletAdapterSolanaEd25519TokenSigner(solanaConfig.wallet),
107-
config.dialectCloud.v2Url,
113+
v2AuthApi,
108114
);
109115
}
110116
if (solanaConfig.wallet.canSignTransaction()) {
111117
return new SolanaSignTxV2AuthenticationFacadeFactory(
112118
new DialectWalletAdapterSolanaTxSigner(solanaConfig.wallet),
113-
config.dialectCloud.v2Url,
119+
v2AuthApi,
114120
);
115121
}
116122
}
117-
throw new IllegalArgumentError('Unsupported authentication');
123+
throw new IllegalArgumentError(
124+
`Unable to create authentication facade, ${config.dialectCloud.apiVersion} is not supported`,
125+
);
118126
}
119127

120128
private initializeSolanaConfig(): SolanaConfig {

packages/sdk/src/dialect-cloud-api/data-service-errors.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,3 +52,5 @@ export async function withErrorParsing<T>(
5252
function createMessage(e: ApiError) {
5353
return `${e.message ?? JSON.stringify(e.error)}`;
5454
}
55+
56+
export const withApiErrorParsing = withErrorParsing;

packages/sdk/src/dialect-cloud-api/v2/auth-api.ts

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,4 @@
1-
import {
2-
ApiError,
3-
createHeaders,
4-
NetworkError,
5-
XRequestIdHeader,
6-
} from '../utils';
1+
import { ApiError, createHeaders, XRequestIdHeader } from '../utils';
72

83
export interface AuthResponse {
94
walletAddress: string;
@@ -69,8 +64,12 @@ export class AuthV2Api {
6964
);
7065
}
7166

72-
async getAuth(): Promise<AuthResponse> {
73-
return this.fetch<AuthResponse>('/v2/auth');
67+
async getAuth(token: string): Promise<AuthResponse> {
68+
return this.fetch<AuthResponse>('/v2/auth', {
69+
headers: {
70+
Authorization: `Bearer ${token}`,
71+
},
72+
});
7473
}
7574

7675
async prepareSolanaAuth(walletAddress: string): Promise<AuthPrepareResponse> {

packages/sdk/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,4 @@ export * from './dialect-cloud-api/v2/auth-api';
3434
export * from './version';
3535
export * from './dialect-cloud-api/smart-message-spec.dto';
3636
export * from './dialect-cloud-api/smart-message.dto';
37+
export * from './dialect-cloud-api/data-service-errors';

0 commit comments

Comments
 (0)