Skip to content

Commit

Permalink
add utils for signature and auth header + add keyed parameters to client
Browse files Browse the repository at this point in the history
  • Loading branch information
SalmaElsoly committed Feb 18, 2025
1 parent b737d50 commit a546fa6
Show file tree
Hide file tree
Showing 12 changed files with 151 additions and 251 deletions.
31 changes: 18 additions & 13 deletions packages/registrar_client/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,29 +4,34 @@ This package provides a client for interacting with the TFGrid v4 Node Registrar

## Getting Started

Set the `REGISTRAR_URL` in your environment variables to point to the TFGrid v4 Node Registrar.
To initialize the Registrar Client, you need to provide the base url of registrar and your private key. Here is an example:

```sh
export REGISTRAR_URL=https://your-registrar-url
```typescript
const client = new RegistrarClient({baseURl:"https://registrar.dev4.grid.tf/v1" , privateKey: your_private_key});
```

## Usage

Here is an example of how to use the Registrar Client:

```typescript
const privateKey = "your_private_key";
const client = new RegistrarClient(privateKey);
const client = new RegistrarClient({baseUrl: URl, privateKey: your_private_key});

// Example: Create an account
const accountRequest: CreateAccountRequest = {
// your create account request data
};
client.account
.createAccount(accountRequest)
.then(account => {
console.log("Account created:", account);
})
.catch(error => {
console.error("Failed to create account:", error);
});

const account = await client.account.createAccount(accountRequest);

// Example: Get account details
const twinID = account.twin_id;
const accountDetails = await client.account.getAccountByTwinId(twinID);

// Example: Update account information
const updateAccountRequest: UpdateAccountRequest = {
// your update account request data
};

const updatedAccount = await client.account.updateAccount(twinID, updateAccountRequest);
```
5 changes: 0 additions & 5 deletions packages/registrar_client/jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,6 @@ module.exports = {
"^.+\\.(ts|tsx)$": ["ts-jest", { useESM: true }],
},
extensionsToTreatAsEsm: [".ts", ".tsx"],
globals: {
"ts-jest": {
useESM: true,
},
},
testEnvironment: "node",
setupFiles: ["dotenv/config"],
};
8 changes: 5 additions & 3 deletions packages/registrar_client/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "registrar_client",
"version": "1.0.0",
"description": "Now I’m the model of a modern major general / The venerated Virginian veteran whose men are all / Lining up, to put me up on a pedestal / Writin’ letters to relatives / Embellishin’ my elegance and eloquence / But the elephant is in the room / The truth is in ya face when ya hear the British cannons go / BOOM",
"version": "0.1.0",
"description": "Registrar client for the ThreeFold Grid V4",
"keywords": [],
"author": "Salma Elsoly <[email protected]>",
"license": "ISC",
Expand All @@ -18,7 +18,9 @@
"url": "git+https://github.com/threefoldtech/tfgridv4-sdk-ts.git"
},
"scripts": {
"test": "node ./__tests__/registrar_client.test.js"
"test": "jest",
"format": "prettier --write .",
"lint": "eslint src/**/*.ts"
},
"bugs": {
"url": "https://github.com/threefoldtech/tfgridv4-sdk-ts/issues"
Expand Down
23 changes: 16 additions & 7 deletions packages/registrar_client/src/client/client.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
import axios, { AxiosInstance, AxiosRequestConfig } from "axios";
import { Accounts } from "../modules/account/service";
import { Config } from "../config";
import { Farms } from "../modules/farm/service";
import { Nodes } from "../modules/node/service";
import { Zos } from "../modules/zos/service";

export abstract class BaseClient {
private client: AxiosInstance;

constructor() {
constructor(baseURL: string) {
this.client = axios.create({
baseURL: Config.getInstance().registrarUrl,
baseURL: baseURL,
});
}

Expand All @@ -34,17 +33,27 @@ export abstract class BaseClient {
return response.data;
}
}
interface Config {
baseURL: string;
privateKey: string;
}

export class RegistrarClient extends BaseClient {
public readonly private_key: string;
public readonly privateKey: string;
accounts: Accounts;
farms: Farms;
nodes: Nodes;
zos: Zos;

constructor(private_key: string) {
super();
this.private_key = private_key;
constructor({ baseURL, privateKey }: Config) {
if (!baseURL) {
throw new Error("Base URL is required");
}
if (!privateKey) {
throw new Error("Private key is required");
}
super(baseURL);
this.privateKey = privateKey;
this.accounts = new Accounts(this);
this.farms = new Farms(this);
this.nodes = new Nodes(this);
Expand Down
18 changes: 0 additions & 18 deletions packages/registrar_client/src/config.ts

This file was deleted.

87 changes: 25 additions & 62 deletions packages/registrar_client/src/modules/account/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { RegistrarClient } from "../../client/client";
import { Account, CreateAccountRequest, UpdateAccountRequest } from "./types";
import * as tweetnacl from "tweetnacl";
import * as base64 from "base64-js";
import { createSignatureWithPublicKey, createAuthHeader } from "../../utils";

export class Accounts {
private client: RegistrarClient;
Expand All @@ -15,81 +16,43 @@ export class Accounts {
async createAccount(request: Partial<CreateAccountRequest>): Promise<Account | null> {
const timestamp = Math.floor(Date.now() / 1000);

const privateKey = this.client.private_key;
let publicKey;
try {
publicKey = base64.fromByteArray(tweetnacl.sign.keyPair.fromSecretKey(base64.toByteArray(privateKey)).publicKey);
} catch (e) {
console.error("Failed to generate public key: ", e);
return null;
}
const privateKey = this.client.privateKey;
const keyPair = tweetnacl.sign.keyPair.fromSecretKey(base64.toByteArray(privateKey));

const challenge = `${timestamp}:${publicKey}`;
const signature = base64.fromByteArray(
tweetnacl.sign.detached(Buffer.from(challenge, "utf-8"), base64.toByteArray(privateKey)),
);
const publicKey = base64.fromByteArray(keyPair.publicKey);
const signature = createSignatureWithPublicKey(timestamp, publicKey, privateKey);

request.public_key = publicKey;
request.signature = signature;
request.timestamp = timestamp;

try {
const data = await this.client.post<Account>(this.accountUri, request);
return data;
} catch (e) {
console.error("Failed to create account: ", e);
return null;
}
const data = await this.client.post<Account>(this.accountUri, request);
return data;
}

async getAccountByPublicKey(publicKey: string): Promise<Account | null> {
try {
const data = await this.client.get<Account>(this.accountUri, {
params: {
public_key: publicKey,
},
});
return data;
} catch (e) {
console.error("Failed to get account: ", e);
return null;
}
async getAccountByPublicKey(publicKey: string): Promise<Account> {
const data = await this.client.get<Account>(this.accountUri, {
params: {
public_key: publicKey,
},
});
return data;
}

async getAccountByTwinId(twinId: number): Promise<Account | null> {
try {
const data = await this.client.get<Account>(this.accountUri, {
params: {
twin_id: twinId,
},
});
async getAccountByTwinId(twinId: number): Promise<Account> {
const data = await this.client.get<Account>(this.accountUri, {
params: {
twin_id: twinId,
},
});

return data;
} catch (e) {
console.error("Failed to get account: ", e);
return null;
}
return data;
}

async updateAccount(twinID: number, body: UpdateAccountRequest): Promise<any> {
const timestamp = Math.floor(Date.now() / 1000);
const challenge = `${timestamp}:${twinID}`;
const privateKey = this.client.private_key;
if (!privateKey) {
throw new Error("Private key is not found");
}
const signature = tweetnacl.sign.detached(Buffer.from(challenge, "utf-8"), base64.toByteArray(privateKey));
const config = {
headers: {
"X-Auth": `${Buffer.from(challenge).toString("base64")}:${base64.fromByteArray(signature)}`,
},
};
try {
const data = await this.client.patch<any>(`${this.accountUri}/${twinID}`, body, config);
return data;
} catch (e) {
console.error("Failed to update account: ", e);
return null;
}
const headers = createAuthHeader(twinID, this.client.privateKey);

const data = await this.client.patch<any>(`${this.accountUri}/${twinID}`, body, { headers });
return data;
}
}
72 changes: 15 additions & 57 deletions packages/registrar_client/src/modules/farm/service.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { RegistrarClient } from "../../client/client";
import { Farm, FarmsFilter } from "./types";
import * as tweetnacl from "tweetnacl";
import * as base64 from "base64-js";
import { createAuthHeader } from "../../utils";

export class Farms {
private client: RegistrarClient;
Expand All @@ -11,7 +10,7 @@ export class Farms {
this.client = client;
}

async createFarm(farm: Partial<Farm>): Promise<number | null> {
async createFarm(farm: Partial<Farm>): Promise<number> {
const twinID = farm.twin_id;
if (!twinID) {
throw new Error("TwinID is not found");
Expand All @@ -20,70 +19,29 @@ export class Farms {
if (!farmName || farmName.length < 1 || farmName.length > 40) {
throw new Error("Farm name must be between 1 and 40 characters");
}
const timestamp = Math.floor(Date.now() / 1000);
const challenge = `${timestamp}:${twinID}`;
const privateKey = this.client.private_key;
if (!privateKey) {
throw new Error("Private key is not found");
}
const signature = tweetnacl.sign.detached(Buffer.from(challenge, "utf-8"), base64.toByteArray(privateKey));
const config = {
headers: {
"X-Auth": `${Buffer.from(challenge).toString("base64")}:${base64.fromByteArray(signature)}`,
},
};
const headers = createAuthHeader(twinID, this.client.privateKey);

try {
const data = await this.client.post<number>(this.farmUri, farm, config);
return data;
} catch (e) {
console.error("Failed to create farm: ", e);
return null;
}
const data = await this.client.post<number>(this.farmUri, farm, { headers });
return data;
}

async listFarms(filter: FarmsFilter): Promise<Farm[] | null> {
try {
const data = await this.client.get<Farm[]>(this.farmUri, { params: filter });
return data;
} catch (e) {
console.error("Failed to get farms: ", e);
return null;
}
async listFarms(filter: FarmsFilter): Promise<Farm[]> {
const data = await this.client.get<Farm[]>(this.farmUri, { params: filter });
return data;
}

async getFarm(farmID: number): Promise<Farm | null> {
try {
const data = await this.client.get<Farm>(`${this.farmUri}/${farmID}`);
return data;
} catch (e) {
console.error("Failed to get farm: ", e);
return null;
}
async getFarm(farmID: number): Promise<Farm> {
const data = await this.client.get<Farm>(`${this.farmUri}/${farmID}`);
return data;
}

async updateFarm(farmID: number, twinID: number, name: string): Promise<any> {
if (!name || name.length < 1 || name.length > 40) {
throw new Error("Farm name must be between 1 and 40 characters");
}
const timestamp = Math.floor(Date.now() / 1000);
const challenge = `${timestamp}:${twinID}`;
const privateKey = this.client.private_key;
if (!privateKey) {
throw new Error("Private key is not found");
}
const signature = tweetnacl.sign.detached(Buffer.from(challenge, "utf-8"), base64.toByteArray(privateKey));
const config = {
headers: {
"X-Auth": `${Buffer.from(challenge).toString("base64")}:${base64.fromByteArray(signature)}`,
},
};
try {
const data = await this.client.patch<any>(`${this.farmUri}/${farmID}`, { farm_name: name }, config);
return data;
} catch (e) {
console.error("Failed to update farm: ", e);
return null;
}
const headers = createAuthHeader(twinID, this.client.privateKey);

const data = await this.client.patch<any>(`${this.farmUri}/${farmID}`, { farm_name: name }, { headers });
return data;
}
}
Loading

0 comments on commit a546fa6

Please sign in to comment.