@super-protocol/sp-cli 0.0.9 → 0.0.10-beta
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +191 -163
- package/dist/commands/account/base.d.ts +3 -4
- package/dist/commands/account/base.js +8 -6
- package/dist/commands/account/forget.js +3 -3
- package/dist/commands/account/get-sppi.js +7 -11
- package/dist/commands/account/info.d.ts +1 -13
- package/dist/commands/account/info.js +20 -40
- package/dist/commands/account/list.js +6 -6
- package/dist/commands/account/login.d.ts +6 -5
- package/dist/commands/account/login.js +96 -145
- package/dist/commands/account/switch.js +2 -2
- package/dist/commands/assets/base.d.ts +39 -0
- package/dist/commands/assets/base.js +217 -0
- package/dist/commands/assets/create.d.ts +41 -0
- package/dist/commands/assets/create.js +277 -0
- package/dist/commands/assets/delete.d.ts +14 -0
- package/dist/commands/assets/delete.js +69 -0
- package/dist/commands/assets/get.d.ts +14 -0
- package/dist/commands/assets/get.js +79 -0
- package/dist/commands/assets/list.d.ts +7 -0
- package/dist/commands/assets/list.js +33 -0
- package/dist/commands/assets/update.d.ts +44 -0
- package/dist/commands/assets/update.js +321 -0
- package/dist/commands/base.d.ts +1 -0
- package/dist/commands/base.js +5 -0
- package/dist/config/config.schema.d.ts +2 -11
- package/dist/config/config.schema.js +2 -7
- package/dist/constants.d.ts +3 -3
- package/dist/constants.js +3 -3
- package/dist/errors.d.ts +0 -2
- package/dist/errors.js +0 -2
- package/dist/hooks/prerun/auth.js +3 -8
- package/dist/interfaces/config-manager.interface.d.ts +3 -1
- package/dist/lib/container.d.ts +4 -12
- package/dist/lib/container.js +28 -113
- package/dist/lib/swarm-client/fetch-api.d.ts +7 -0
- package/dist/lib/swarm-client/fetch-api.js +41 -0
- package/dist/lib/swarm-client/fetch-timeout.client.d.ts +1 -0
- package/dist/lib/swarm-client/fetch-timeout.client.js +32 -0
- package/dist/lib/swarm-client/index.d.ts +6 -0
- package/dist/lib/swarm-client/index.js +31 -0
- package/dist/lib/swarm-client/middlewares/authorization.middleware.d.ts +2 -0
- package/dist/lib/swarm-client/middlewares/authorization.middleware.js +12 -0
- package/dist/lib/swarm-client/middlewares/index.d.ts +6 -0
- package/dist/lib/swarm-client/middlewares/index.js +5 -0
- package/dist/lib/swarm-client/middlewares/logger.middleware.d.ts +2 -0
- package/dist/lib/swarm-client/middlewares/logger.middleware.js +30 -0
- package/dist/lib/swarm-client/middlewares/request-id.middleware.d.ts +2 -0
- package/dist/lib/swarm-client/middlewares/request-id.middleware.js +13 -0
- package/dist/lib/swarm-client/types.d.ts +23 -0
- package/dist/lib/swarm-client/types.js +1 -0
- package/dist/managers/account-manager.d.ts +1 -0
- package/dist/managers/account-manager.js +13 -18
- package/dist/managers/config-file-manager.d.ts +22 -9
- package/dist/managers/config-file-manager.js +247 -122
- package/dist/managers/config-manager.d.ts +6 -6
- package/dist/managers/config-manager.js +8 -8
- package/dist/services/account.service.d.ts +42 -0
- package/dist/services/account.service.js +140 -0
- package/dist/services/asset.service.d.ts +35 -0
- package/dist/services/asset.service.js +120 -0
- package/dist/services/auth.service.d.ts +4 -6
- package/dist/services/auth.service.js +108 -118
- package/dist/utils/helper.js +2 -2
- package/oclif.manifest.json +462 -212
- package/package.json +7 -8
- package/dist/commands/files/download.d.ts +0 -15
- package/dist/commands/files/download.js +0 -63
- package/dist/commands/files/upload.d.ts +0 -18
- package/dist/commands/files/upload.js +0 -83
- package/dist/commands/storage/base.d.ts +0 -13
- package/dist/commands/storage/base.js +0 -125
- package/dist/commands/storage/create.d.ts +0 -11
- package/dist/commands/storage/create.js +0 -53
- package/dist/commands/storage/select.d.ts +0 -9
- package/dist/commands/storage/select.js +0 -38
- package/dist/commands/storage/show.d.ts +0 -17
- package/dist/commands/storage/show.js +0 -34
- package/dist/commands/storage/update.d.ts +0 -14
- package/dist/commands/storage/update.js +0 -204
- package/dist/commands/workflows/extend-lease.d.ts +0 -17
- package/dist/commands/workflows/extend-lease.js +0 -102
- package/dist/hooks/finally/shutdown-blockchain.d.ts +0 -3
- package/dist/hooks/finally/shutdown-blockchain.js +0 -8
- package/dist/middlewares/auth-middleware.d.ts +0 -9
- package/dist/middlewares/auth-middleware.js +0 -91
- package/dist/middlewares/cookies-middleware.d.ts +0 -8
- package/dist/middlewares/cookies-middleware.js +0 -80
- package/dist/services/storage.service.d.ts +0 -73
- package/dist/services/storage.service.js +0 -378
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { Value } from 'typebox/value';
|
|
3
|
+
import { privateKeyToAccount } from 'viem/accounts';
|
|
4
|
+
import { cliConfigSchema } from '../config/config.schema.js';
|
|
5
|
+
import { SWARM_URL } from '../constants.js';
|
|
6
|
+
import { getConfigName, getConfigNameFromCredentials, readJsonFile } from '../utils/helper.js';
|
|
7
|
+
export class AccountServiceError extends Error {
|
|
8
|
+
}
|
|
9
|
+
export class InvalidPrivateKeyError extends AccountServiceError {
|
|
10
|
+
}
|
|
11
|
+
export class InvalidAccountConfigError extends AccountServiceError {
|
|
12
|
+
}
|
|
13
|
+
export class AccountService {
|
|
14
|
+
configFileManager;
|
|
15
|
+
constructor(configFileManager) {
|
|
16
|
+
this.configFileManager = configFileManager;
|
|
17
|
+
}
|
|
18
|
+
createAccountFromKey(privateKey) {
|
|
19
|
+
try {
|
|
20
|
+
const account = privateKeyToAccount(privateKey);
|
|
21
|
+
return {
|
|
22
|
+
address: account.address,
|
|
23
|
+
privateKey: privateKey,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
throw new InvalidPrivateKeyError('Invalid private key provided.');
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
async loadConfigFromPath(configPath) {
|
|
31
|
+
try {
|
|
32
|
+
const config = await readJsonFile({ path: configPath });
|
|
33
|
+
if (!Value.Check(cliConfigSchema, config)) {
|
|
34
|
+
throw new InvalidAccountConfigError("Account doesn't match required schema");
|
|
35
|
+
}
|
|
36
|
+
if (!config.account?.privateKey) {
|
|
37
|
+
throw new InvalidAccountConfigError('Account private key is required for import.');
|
|
38
|
+
}
|
|
39
|
+
return config;
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
if (error instanceof AccountServiceError) {
|
|
43
|
+
throw error;
|
|
44
|
+
}
|
|
45
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
46
|
+
throw new InvalidAccountConfigError(message);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
resolveConfigFileName(configPath, config, name) {
|
|
50
|
+
if (name) {
|
|
51
|
+
return getConfigName(name);
|
|
52
|
+
}
|
|
53
|
+
const normalizedName = config.name?.trim();
|
|
54
|
+
if (normalizedName) {
|
|
55
|
+
return getConfigName(normalizedName);
|
|
56
|
+
}
|
|
57
|
+
if (config.account?.privateKey) {
|
|
58
|
+
const swarmUrl = (config.swarmUrl ?? SWARM_URL).trim();
|
|
59
|
+
return getConfigNameFromCredentials(config.account.privateKey, swarmUrl);
|
|
60
|
+
}
|
|
61
|
+
const baseName = path.basename(configPath, path.extname(configPath));
|
|
62
|
+
return getConfigName(baseName);
|
|
63
|
+
}
|
|
64
|
+
async configExists(configFile) {
|
|
65
|
+
return this.configFileManager.configExists(configFile);
|
|
66
|
+
}
|
|
67
|
+
async getConfigsWithNames() {
|
|
68
|
+
return this.configFileManager.getConfigsWithNames();
|
|
69
|
+
}
|
|
70
|
+
async getConfigData(configFile) {
|
|
71
|
+
return this.configFileManager.getConfigData(configFile);
|
|
72
|
+
}
|
|
73
|
+
async setCurrentConfig(configFile) {
|
|
74
|
+
await this.configFileManager.setCurrentConfig(configFile);
|
|
75
|
+
}
|
|
76
|
+
async importConfig(sourcePath, name) {
|
|
77
|
+
return this.configFileManager.importConfig(sourcePath, name);
|
|
78
|
+
}
|
|
79
|
+
async createConfigIfMissing(configFile, displayName, swarmUrl, account, authUrl) {
|
|
80
|
+
if (await this.configExists(configFile)) {
|
|
81
|
+
const existingConfig = await this.configFileManager.getConfigData(configFile);
|
|
82
|
+
const existingKey = existingConfig?.account?.privateKey?.trim().toLowerCase();
|
|
83
|
+
const existingUrl = (existingConfig?.swarmUrl ?? SWARM_URL).trim();
|
|
84
|
+
const newKey = account.privateKey.trim().toLowerCase();
|
|
85
|
+
if (!existingKey || existingKey !== newKey || existingUrl !== swarmUrl) {
|
|
86
|
+
throw new Error(`Account name "${displayName}" is already used by another account.`);
|
|
87
|
+
}
|
|
88
|
+
const nameUpdate = await this.configFileManager.updateConfigName(configFile, displayName);
|
|
89
|
+
const updatedAuthUrl = authUrl
|
|
90
|
+
? await this.configFileManager.updateConfigAuthUrl(nameUpdate.fileName, authUrl)
|
|
91
|
+
: false;
|
|
92
|
+
return {
|
|
93
|
+
created: false,
|
|
94
|
+
updatedName: nameUpdate.updated,
|
|
95
|
+
updatedAuthUrl,
|
|
96
|
+
configFile: nameUpdate.fileName,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
const createdFile = await this.configFileManager.createConfig(configFile, displayName, swarmUrl, account, authUrl);
|
|
100
|
+
return {
|
|
101
|
+
created: true,
|
|
102
|
+
updatedName: false,
|
|
103
|
+
updatedAuthUrl: Boolean(authUrl),
|
|
104
|
+
configFile: createdFile,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
async updateConfigDisplayName(configFile, displayName) {
|
|
108
|
+
if (!displayName) {
|
|
109
|
+
return { status: 'skipped', configFile };
|
|
110
|
+
}
|
|
111
|
+
const updated = await this.configFileManager.updateConfigName(configFile, displayName);
|
|
112
|
+
return {
|
|
113
|
+
status: updated.updated ? 'updated' : 'unchanged',
|
|
114
|
+
configFile: updated.fileName,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
async updateConfigAuthUrl(configFile, authUrl) {
|
|
118
|
+
if (!authUrl) {
|
|
119
|
+
return 'skipped';
|
|
120
|
+
}
|
|
121
|
+
const updated = await this.configFileManager.updateConfigAuthUrl(configFile, authUrl);
|
|
122
|
+
return updated ? 'updated' : 'unchanged';
|
|
123
|
+
}
|
|
124
|
+
async findConfigsByPrivateKey(privateKey, swarmUrl, matchSwarmUrl) {
|
|
125
|
+
const normalizedKey = privateKey.trim().toLowerCase();
|
|
126
|
+
const configs = await this.configFileManager.getConfigsWithNames();
|
|
127
|
+
const matches = [];
|
|
128
|
+
for (const config of configs) {
|
|
129
|
+
const configData = await this.configFileManager.getConfigData(config.file);
|
|
130
|
+
const storedKey = configData?.account?.privateKey?.toLowerCase();
|
|
131
|
+
const storedUrl = (configData?.swarmUrl ?? SWARM_URL).trim();
|
|
132
|
+
const keyMatches = storedKey === normalizedKey;
|
|
133
|
+
const urlMatches = !matchSwarmUrl || storedUrl === swarmUrl;
|
|
134
|
+
if (keyMatches && urlMatches) {
|
|
135
|
+
matches.push(config);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return matches;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type pino from 'pino';
|
|
2
|
+
import type { z } from 'zod';
|
|
3
|
+
import { AssetCreateSchema, type AssetSchema, AssetUpdateSchema, type DockerHubCredentialsSchema, type GitHubCredentialsSchema, type GoogleDocsCredentialsSchema, type S3CredentialsSchema, type StorjCredentialsSchema, type SwarmClient } from '../lib/swarm-client/index.js';
|
|
4
|
+
export { SourceType, AssetType } from '../lib/swarm-client/index.js';
|
|
5
|
+
export declare class AssetError extends Error {
|
|
6
|
+
}
|
|
7
|
+
export declare class AssetNotFoundError extends AssetError {
|
|
8
|
+
}
|
|
9
|
+
export declare class AssetValidationError extends AssetError {
|
|
10
|
+
}
|
|
11
|
+
export type Asset = z.infer<typeof AssetSchema>;
|
|
12
|
+
export type AssetCreateInput = z.infer<typeof AssetCreateSchema>;
|
|
13
|
+
export type AssetUpdateInput = z.infer<typeof AssetUpdateSchema>;
|
|
14
|
+
export type StorjCredentials = z.infer<typeof StorjCredentialsSchema>;
|
|
15
|
+
export type S3Credentials = z.infer<typeof S3CredentialsSchema>;
|
|
16
|
+
export type DockerHubCredentials = z.infer<typeof DockerHubCredentialsSchema>;
|
|
17
|
+
export type GitHubCredentials = z.infer<typeof GitHubCredentialsSchema>;
|
|
18
|
+
export type GoogleDocsCredentials = z.infer<typeof GoogleDocsCredentialsSchema>;
|
|
19
|
+
export type AssetCredentials = StorjCredentials | S3Credentials | DockerHubCredentials | GitHubCredentials | GoogleDocsCredentials;
|
|
20
|
+
export declare class AssetService {
|
|
21
|
+
private readonly swarmClient;
|
|
22
|
+
private readonly logger;
|
|
23
|
+
constructor(swarmClient: SwarmClient, logger: pino.BaseLogger);
|
|
24
|
+
validateCreatePayload(payload: unknown): AssetCreateInput;
|
|
25
|
+
validateUpdatePayload(payload: unknown): AssetUpdateInput;
|
|
26
|
+
createAsset(payload: AssetCreateInput): Promise<Asset>;
|
|
27
|
+
deleteAsset(id: string): Promise<{
|
|
28
|
+
success: boolean;
|
|
29
|
+
}>;
|
|
30
|
+
findAssetById(id: string): Promise<Asset | undefined>;
|
|
31
|
+
findAssetByName(name: string): Promise<Asset | undefined>;
|
|
32
|
+
listAssets(): Promise<Asset[]>;
|
|
33
|
+
updateAsset(id: string, payload: AssetUpdateInput): Promise<Asset>;
|
|
34
|
+
private formatErrorMessage;
|
|
35
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { AssetCreateSchema, AssetUpdateSchema, } from '../lib/swarm-client/index.js';
|
|
2
|
+
export { SourceType, AssetType } from '../lib/swarm-client/index.js';
|
|
3
|
+
export class AssetError extends Error {
|
|
4
|
+
}
|
|
5
|
+
export class AssetNotFoundError extends AssetError {
|
|
6
|
+
}
|
|
7
|
+
export class AssetValidationError extends AssetError {
|
|
8
|
+
}
|
|
9
|
+
const isRecord = (value) => Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
10
|
+
export class AssetService {
|
|
11
|
+
swarmClient;
|
|
12
|
+
logger;
|
|
13
|
+
constructor(swarmClient, logger) {
|
|
14
|
+
this.swarmClient = swarmClient;
|
|
15
|
+
this.logger = logger;
|
|
16
|
+
}
|
|
17
|
+
validateCreatePayload(payload) {
|
|
18
|
+
const asset = AssetCreateSchema.safeParse(payload);
|
|
19
|
+
if (asset.success) {
|
|
20
|
+
return asset.data;
|
|
21
|
+
}
|
|
22
|
+
this.logger.error(asset.error);
|
|
23
|
+
throw new AssetValidationError('Incorrect input');
|
|
24
|
+
}
|
|
25
|
+
validateUpdatePayload(payload) {
|
|
26
|
+
const asset = AssetUpdateSchema.safeParse(payload);
|
|
27
|
+
if (asset.success) {
|
|
28
|
+
return asset.data;
|
|
29
|
+
}
|
|
30
|
+
this.logger.error(asset.error);
|
|
31
|
+
throw new AssetValidationError('Incorrect input');
|
|
32
|
+
}
|
|
33
|
+
async createAsset(payload) {
|
|
34
|
+
const sanitized = this.validateCreatePayload(payload);
|
|
35
|
+
const createResponse = await this.swarmClient.asset.create({
|
|
36
|
+
body: sanitized,
|
|
37
|
+
});
|
|
38
|
+
if (createResponse.status !== 201) {
|
|
39
|
+
throw new AssetError(this.formatErrorMessage(createResponse.body, 'Asset create failed'));
|
|
40
|
+
}
|
|
41
|
+
const body = createResponse.body;
|
|
42
|
+
this.logger.info({ assetId: body.id }, 'Asset created');
|
|
43
|
+
return body;
|
|
44
|
+
}
|
|
45
|
+
async deleteAsset(id) {
|
|
46
|
+
const response = await this.swarmClient.asset.delete({
|
|
47
|
+
params: { id },
|
|
48
|
+
});
|
|
49
|
+
if (response.status === 404) {
|
|
50
|
+
throw new AssetNotFoundError(`Asset not found: ${id}`);
|
|
51
|
+
}
|
|
52
|
+
if (response.status !== 200) {
|
|
53
|
+
throw new AssetError(this.formatErrorMessage(response.body, 'Asset delete failed'));
|
|
54
|
+
}
|
|
55
|
+
const result = response.body;
|
|
56
|
+
if (!result.success) {
|
|
57
|
+
throw new AssetError('Asset delete failed');
|
|
58
|
+
}
|
|
59
|
+
this.logger.info({ assetId: id }, 'Asset deleted');
|
|
60
|
+
return result;
|
|
61
|
+
}
|
|
62
|
+
async findAssetById(id) {
|
|
63
|
+
const response = await this.swarmClient.asset.get({
|
|
64
|
+
params: { id },
|
|
65
|
+
});
|
|
66
|
+
if (response.status === 404) {
|
|
67
|
+
return undefined;
|
|
68
|
+
}
|
|
69
|
+
if (response.status !== 200) {
|
|
70
|
+
throw new AssetError(this.formatErrorMessage(response.body, 'Asset lookup failed'));
|
|
71
|
+
}
|
|
72
|
+
return response.body;
|
|
73
|
+
}
|
|
74
|
+
async findAssetByName(name) {
|
|
75
|
+
const response = await this.swarmClient.asset.search({
|
|
76
|
+
query: { name },
|
|
77
|
+
});
|
|
78
|
+
if (response.status !== 200) {
|
|
79
|
+
throw new AssetError(this.formatErrorMessage(response.body, 'Asset lookup failed'));
|
|
80
|
+
}
|
|
81
|
+
const [assets] = response.body;
|
|
82
|
+
return assets;
|
|
83
|
+
}
|
|
84
|
+
async listAssets() {
|
|
85
|
+
const response = await this.swarmClient.asset.search();
|
|
86
|
+
if (response.status !== 200) {
|
|
87
|
+
throw new AssetError(this.formatErrorMessage(response.body, 'Asset list failed'));
|
|
88
|
+
}
|
|
89
|
+
return response.body;
|
|
90
|
+
}
|
|
91
|
+
async updateAsset(id, payload) {
|
|
92
|
+
const sanitized = this.validateUpdatePayload(payload);
|
|
93
|
+
const updateResponse = await this.swarmClient.asset.update({
|
|
94
|
+
body: sanitized,
|
|
95
|
+
params: { id },
|
|
96
|
+
});
|
|
97
|
+
if (updateResponse.status === 404) {
|
|
98
|
+
throw new AssetNotFoundError(`Asset not found: ${id}`);
|
|
99
|
+
}
|
|
100
|
+
if (updateResponse.status !== 200) {
|
|
101
|
+
throw new AssetError(this.formatErrorMessage(updateResponse.body, 'Asset update failed'));
|
|
102
|
+
}
|
|
103
|
+
const updated = updateResponse.body;
|
|
104
|
+
this.logger.info({ assetId: id }, 'Asset updated');
|
|
105
|
+
return updated;
|
|
106
|
+
}
|
|
107
|
+
formatErrorMessage(body, fallbackMessage) {
|
|
108
|
+
if (!isRecord(body)) {
|
|
109
|
+
return fallbackMessage;
|
|
110
|
+
}
|
|
111
|
+
const message = body.message;
|
|
112
|
+
if (Array.isArray(message)) {
|
|
113
|
+
return message.join(', ');
|
|
114
|
+
}
|
|
115
|
+
if (typeof message === 'string' && message.trim()) {
|
|
116
|
+
return message;
|
|
117
|
+
}
|
|
118
|
+
return fallbackMessage;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type { ProviderClient } from '@super-protocol/provider-client';
|
|
2
1
|
import type pino from 'pino';
|
|
3
2
|
import type { AccountManager } from '../managers/account-manager.js';
|
|
4
3
|
import type { ConfigManager } from '../managers/config-manager.js';
|
|
@@ -7,13 +6,12 @@ export declare class AuthError extends Error {
|
|
|
7
6
|
export declare class AuthService {
|
|
8
7
|
private readonly accountManager;
|
|
9
8
|
private readonly configManager;
|
|
10
|
-
private readonly providerClient;
|
|
11
9
|
private readonly logger;
|
|
12
|
-
|
|
13
|
-
|
|
10
|
+
private authClient;
|
|
11
|
+
constructor(accountManager: AccountManager, configManager: ConfigManager, logger: pino.BaseLogger);
|
|
12
|
+
private buildSiweMessage;
|
|
13
|
+
private hasActiveSession;
|
|
14
14
|
auth(options?: {
|
|
15
15
|
force?: boolean;
|
|
16
16
|
}): Promise<void>;
|
|
17
|
-
private getTokens;
|
|
18
|
-
private getUserNonce;
|
|
19
17
|
}
|
|
@@ -1,159 +1,149 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { createAuthClient, } from 'better-auth/client';
|
|
2
|
+
import { siweClient } from 'better-auth/client/plugins';
|
|
3
|
+
import { AUTH_PATH } from '../constants.js';
|
|
4
|
+
const DEFAULT_CHAIN_ID = 1;
|
|
5
|
+
const SIWE_VERSION = '1';
|
|
4
6
|
export class AuthError extends Error {
|
|
5
7
|
}
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
const getErrorMessage = (error, fallback) => {
|
|
9
|
+
if (!error) {
|
|
10
|
+
return fallback;
|
|
11
|
+
}
|
|
12
|
+
if (typeof error === 'string') {
|
|
13
|
+
return error;
|
|
14
|
+
}
|
|
15
|
+
if (typeof error === 'object' && 'message' in error) {
|
|
16
|
+
const message = error.message;
|
|
17
|
+
if (typeof message === 'string' && message.length > 0) {
|
|
18
|
+
return message;
|
|
11
19
|
}
|
|
12
|
-
|
|
13
|
-
|
|
20
|
+
}
|
|
21
|
+
try {
|
|
22
|
+
return JSON.stringify(error);
|
|
14
23
|
}
|
|
15
24
|
catch {
|
|
16
|
-
return
|
|
25
|
+
return fallback;
|
|
17
26
|
}
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
if (
|
|
21
|
-
return
|
|
27
|
+
};
|
|
28
|
+
const getSessionExpiresAt = (value) => {
|
|
29
|
+
if (typeof value === 'number') {
|
|
30
|
+
return Number.isFinite(value) ? value : null;
|
|
22
31
|
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
return
|
|
32
|
+
if (typeof value === 'string') {
|
|
33
|
+
const parsed = Date.parse(value);
|
|
34
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
26
35
|
}
|
|
27
|
-
|
|
28
|
-
|
|
36
|
+
if (value instanceof Date) {
|
|
37
|
+
const time = value.getTime();
|
|
38
|
+
return Number.isFinite(time) ? time : null;
|
|
39
|
+
}
|
|
40
|
+
return null;
|
|
41
|
+
};
|
|
29
42
|
export class AuthService {
|
|
30
43
|
accountManager;
|
|
31
44
|
configManager;
|
|
32
|
-
providerClient;
|
|
33
45
|
logger;
|
|
34
|
-
|
|
46
|
+
authClient;
|
|
47
|
+
constructor(accountManager, configManager, logger) {
|
|
35
48
|
this.accountManager = accountManager;
|
|
36
49
|
this.configManager = configManager;
|
|
37
|
-
this.providerClient = providerClient;
|
|
38
50
|
this.logger = logger;
|
|
51
|
+
this.authClient = createAuthClient({
|
|
52
|
+
baseURL: this.configManager.getAuthUrl(),
|
|
53
|
+
basePath: AUTH_PATH,
|
|
54
|
+
plugins: [siweClient()],
|
|
55
|
+
});
|
|
39
56
|
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
}
|
|
45
|
-
catch (error) {
|
|
46
|
-
this.logger.warn({ err: error }, 'Failed to read cookies from config');
|
|
47
|
-
return false;
|
|
48
|
-
}
|
|
49
|
-
if (!stored || typeof stored !== 'object') {
|
|
50
|
-
return false;
|
|
57
|
+
buildSiweMessage({ address, chainId, domain, issuedAt, nonce, statement, uri, version, }) {
|
|
58
|
+
const lines = [`${domain} wants you to sign in with your Ethereum account:`, address, ''];
|
|
59
|
+
if (statement) {
|
|
60
|
+
lines.push(statement, '');
|
|
51
61
|
}
|
|
62
|
+
lines.push(`URI: ${uri}`, `Version: ${version}`, `Chain ID: ${chainId}`, `Nonce: ${nonce}`, `Issued At: ${issuedAt}`);
|
|
63
|
+
return lines.join('\n');
|
|
64
|
+
}
|
|
65
|
+
async hasActiveSession(accessKey) {
|
|
52
66
|
try {
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
67
|
+
const { data, error } = await this.authClient.getSession({
|
|
68
|
+
fetchOptions: {
|
|
69
|
+
headers: {
|
|
70
|
+
authorization: `Bearer ${accessKey}`,
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
if (error || !data || typeof data !== 'object' || !('session' in data)) {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
const session = data.session;
|
|
78
|
+
if (!session) {
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
const expiresAt = getSessionExpiresAt(session.expiresAt);
|
|
82
|
+
if (expiresAt === null || expiresAt <= Date.now()) {
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
return true;
|
|
56
86
|
}
|
|
57
87
|
catch (error) {
|
|
58
|
-
this.logger.
|
|
88
|
+
this.logger.debug({ err: error }, 'Failed to validate session with access token');
|
|
59
89
|
return false;
|
|
60
90
|
}
|
|
61
91
|
}
|
|
62
92
|
async auth(options = {}) {
|
|
63
93
|
if (!options.force) {
|
|
64
|
-
const credentials =
|
|
94
|
+
const credentials = this.configManager.getCredentials();
|
|
65
95
|
const accessKey = credentials?.accessKey;
|
|
66
|
-
if (accessKey && !isAccessKeyExpired(accessKey)) {
|
|
67
|
-
this.logger.debug('Access key is not expired, skipping authorization');
|
|
68
|
-
return;
|
|
69
|
-
}
|
|
70
96
|
if (accessKey) {
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
this.logger.debug('Access key expired but refresh cookie exists, skipping authorization');
|
|
97
|
+
const hasSession = await this.hasActiveSession(accessKey);
|
|
98
|
+
if (hasSession) {
|
|
99
|
+
this.logger.debug('Access token is valid, skipping authorization');
|
|
75
100
|
return;
|
|
76
101
|
}
|
|
77
102
|
}
|
|
78
103
|
}
|
|
79
104
|
const address = this.accountManager.getAddress();
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
if (signUpError && signUpError.statusCode === 409) {
|
|
89
|
-
this.logger.debug({ signUpError }, 'Error signing up');
|
|
90
|
-
const { error: nonceError, user } = await this.getUserNonce(address);
|
|
91
|
-
this.logger.debug({ nonceError, user }, 'Requesting nonce again');
|
|
92
|
-
if (user?.nonce) {
|
|
93
|
-
await this.getTokens(user.nonce);
|
|
94
|
-
}
|
|
95
|
-
else {
|
|
96
|
-
throw new AuthError('User exists but nonce is unavailable. Please try again or contact support.');
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
else if (signUpError) {
|
|
100
|
-
this.logger.error({ signUpError }, 'Sign-up failed');
|
|
101
|
-
throw new AuthError('Sign-up failed. Please try again later.');
|
|
102
|
-
}
|
|
103
|
-
else {
|
|
104
|
-
const nonce = nonceResponse?.nonce;
|
|
105
|
-
if (!nonce) {
|
|
106
|
-
this.logger.error({ response: nonceResponse }, 'Unexpected sign-up response');
|
|
107
|
-
throw new AuthError('Provider did not return a nonce.');
|
|
108
|
-
}
|
|
109
|
-
await this.getTokens(nonce);
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
else if (user?.nonce) {
|
|
113
|
-
this.logger.debug({ user }, 'Requesting for existed user');
|
|
114
|
-
await this.getTokens(user.nonce);
|
|
105
|
+
const chainId = DEFAULT_CHAIN_ID;
|
|
106
|
+
const { data: nonceResponse, error: nonceError } = await this.authClient.siwe.nonce({
|
|
107
|
+
walletAddress: address,
|
|
108
|
+
chainId,
|
|
109
|
+
});
|
|
110
|
+
if (nonceError) {
|
|
111
|
+
this.logger.error({ nonceError }, 'Failed to request SIWE nonce');
|
|
112
|
+
throw new AuthError(getErrorMessage(nonceError, 'Failed to request SIWE nonce.'));
|
|
115
113
|
}
|
|
116
|
-
|
|
117
|
-
|
|
114
|
+
const nonce = nonceResponse?.nonce;
|
|
115
|
+
if (!nonce) {
|
|
116
|
+
this.logger.error({ response: nonceResponse }, 'Unexpected nonce response');
|
|
117
|
+
throw new AuthError('Provider did not return a nonce.');
|
|
118
118
|
}
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
119
|
+
const providerOrigin = new URL(this.configManager.getSwarmUrl());
|
|
120
|
+
const message = this.buildSiweMessage({
|
|
121
|
+
address,
|
|
122
|
+
chainId,
|
|
123
|
+
domain: providerOrigin.host,
|
|
124
|
+
issuedAt: new Date().toISOString(),
|
|
125
|
+
nonce,
|
|
126
|
+
uri: providerOrigin.origin,
|
|
127
|
+
version: SIWE_VERSION,
|
|
128
|
+
});
|
|
129
|
+
const signature = await this.accountManager.createSign(message);
|
|
130
|
+
const { data: verifyResponse, error: verifyError } = await this.authClient.siwe.verify({
|
|
131
|
+
message,
|
|
132
|
+
signature,
|
|
133
|
+
walletAddress: address,
|
|
134
|
+
chainId,
|
|
128
135
|
});
|
|
129
|
-
if (
|
|
130
|
-
this.logger.error({
|
|
131
|
-
throw new AuthError(
|
|
136
|
+
if (verifyError) {
|
|
137
|
+
this.logger.error({ verifyError }, 'SIWE verification failed');
|
|
138
|
+
throw new AuthError(getErrorMessage(verifyError, 'SIWE verification failed.'));
|
|
132
139
|
}
|
|
133
|
-
const
|
|
134
|
-
if (!
|
|
135
|
-
this.logger.error({ response:
|
|
136
|
-
throw new AuthError('Provider
|
|
140
|
+
const token = verifyResponse?.token;
|
|
141
|
+
if (!token) {
|
|
142
|
+
this.logger.error({ response: verifyResponse }, 'Unexpected SIWE verification response');
|
|
143
|
+
throw new AuthError('Provider did not return a session token.');
|
|
137
144
|
}
|
|
138
145
|
await this.configManager.setCredentials({
|
|
139
|
-
accessKey:
|
|
146
|
+
accessKey: token,
|
|
140
147
|
});
|
|
141
148
|
}
|
|
142
|
-
async getUserNonce(address) {
|
|
143
|
-
try {
|
|
144
|
-
const { data, error } = await this.providerClient.GET('/api/users/nonce/{address}', {
|
|
145
|
-
params: {
|
|
146
|
-
path: {
|
|
147
|
-
address,
|
|
148
|
-
},
|
|
149
|
-
},
|
|
150
|
-
});
|
|
151
|
-
this.logger.debug({ data, error }, 'Getting user nonce');
|
|
152
|
-
return { error, user: data };
|
|
153
|
-
}
|
|
154
|
-
catch (error) {
|
|
155
|
-
this.logger.error({ err: error }, 'Error request nonce');
|
|
156
|
-
throw new AuthError('Connection failure, please try again later');
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
149
|
}
|
package/dist/utils/helper.js
CHANGED
|
@@ -2,7 +2,7 @@ import { createHash } from 'node:crypto';
|
|
|
2
2
|
import { existsSync } from 'node:fs';
|
|
3
3
|
import * as fs from 'node:fs/promises';
|
|
4
4
|
import path from 'node:path';
|
|
5
|
-
export const getConfigName = (name) => `${name.toLowerCase().replaceAll(/[^a-z0-9]+/g, '-')}.
|
|
5
|
+
export const getConfigName = (name) => `${name.toLowerCase().replaceAll(/[^a-z0-9]+/g, '-')}.json`;
|
|
6
6
|
export const getConfigNameFromCredentials = (privateKey, url) => {
|
|
7
7
|
const normalizedKey = privateKey.trim();
|
|
8
8
|
const normalizedUrl = url.trim();
|
|
@@ -14,7 +14,7 @@ export const getConfigNameFromCredentials = (privateKey, url) => {
|
|
|
14
14
|
hashBuilder.update('|');
|
|
15
15
|
hashBuilder.update(normalizedUrl);
|
|
16
16
|
const hash = hashBuilder.digest('hex');
|
|
17
|
-
return `${hash}.
|
|
17
|
+
return `${hash}.json`;
|
|
18
18
|
};
|
|
19
19
|
export const preparePath = (rawPath) => {
|
|
20
20
|
if (path.isAbsolute(rawPath))
|