postiz-app/apps/backend/src/services/auth/providers/wallet.provider.ts
2026-04-17 17:34:34 +07:00

90 lines
2.1 KiB
TypeScript

import {
AuthProvider,
AuthProviderAbstract,
} from '@gitroom/backend/services/auth/providers.interface';
import { randomBytes } from 'crypto';
import { ioRedis } from '@gitroom/nestjs-libraries/redis/redis.service';
import bs58 from 'bs58';
import nacl from 'tweetnacl';
function hexToUint8Array(hex) {
if (hex.startsWith('0x')) {
hex = hex.slice(2);
}
if (hex.length % 2 !== 0) {
throw new Error('Invalid hex string. It must have an even length.');
}
const byteLength = hex.length / 2;
const uint8Array = new Uint8Array(byteLength);
for (let i = 0; i < byteLength; i++) {
const byteHex = hex.substr(i * 2, 2);
uint8Array[i] = parseInt(byteHex, 16);
}
return uint8Array;
}
@AuthProvider({ provider: 'WALLET' })
export class WalletProvider extends AuthProviderAbstract {
async generateLink(params: { publicKey: string }) {
if (!params.publicKey) {
return;
}
const challenge = randomBytes(32).toString('hex');
await ioRedis.set(`wallet:${params.publicKey}`, challenge, 'EX', 60);
return challenge;
}
async getToken(code: string, _redirectUri?: string) {
const { publicKey, challenge, signature } = JSON.parse(
Buffer.from(code, 'base64').toString()
);
if (!publicKey || !challenge || !signature) {
return '';
}
const redisGet = await ioRedis.get(`wallet:${publicKey}`);
if (redisGet !== challenge) {
return '';
}
const publicKeyUint8 = bs58.decode(publicKey);
const messageUint8 = new TextEncoder().encode(challenge);
const signatureUint8 = hexToUint8Array(signature);
const isValid = nacl.sign.detached.verify(
messageUint8,
signatureUint8,
publicKeyUint8
);
if (!isValid) {
return '';
}
return code;
}
async getUser(providerToken: string) {
if ((await this.getToken(providerToken)) === '') {
return {
id: '',
email: '',
};
}
const { publicKey } = JSON.parse(
Buffer.from(providerToken, 'base64').toString()
);
return {
id: String(`wallet_${publicKey}`),
email: String(`wallet_${publicKey}`),
};
}
}