Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion payment-dashboard/src/views/RegistrationPage.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -112,13 +112,15 @@ function RegistrationPage({
);

let signature;
let signerAddress;
try {
const message = `register:${normalizedUsername}:${userPublicKey}`;
const result = await freighterApi.signMessage(message, {
address: userPublicKey,
});
if (result.error) throw new Error(result.error);
signature = result.signedMessage;
signerAddress = result.signerAddress;
} catch (err) {
setStatusMessage(err.message || "Signature request cancelled.", "error");
return;
Expand All @@ -136,12 +138,15 @@ function RegistrationPage({
username: normalizedUsername,
address: userPublicKey,
signature,
signerAddress,
}),
})
.then(async (response) => {
const data = await response.json().catch(() => null);
if (!response.ok) {
throw new Error((data && data.detail) || "Registration failed.");
throw new Error(
(data && (data.error || data.detail || data.message)) || "Registration failed.",
);
}

return data;
Expand Down
88 changes: 70 additions & 18 deletions stellar-payment-platform/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const v1Router = require('./src/routes/v1');
const {verifyMultiSignerThreshold,} = require('./src/multisigner-verifier');
const { poolGet, poolRun, poolAll } = require('./src/db');
const xss = require('xss');
const { StrKey } = require('@stellar/stellar-sdk');
const { Keypair, StrKey } = require('@stellar/stellar-sdk');

dotenv.config();

Expand Down Expand Up @@ -329,6 +329,40 @@ const validateMemo = (memoType, memo) => {
return null;
};

const verifyFreighterRegistrationSignature = ({
username,
address,
signature,
signerAddress,
}) => {
const message = `register:${username}:${address}`;
const keypair = Keypair.fromPublicKey(address);

let signatureBuffer;
if (Buffer.isBuffer(signature)) {
signatureBuffer = signature;
} else if (typeof signature === 'string') {
signatureBuffer = Buffer.from(signature, 'base64');
} else {
throw new Error('Invalid message signature format.');
}

if (!keypair.verify(Buffer.from(message, 'utf8'), signatureBuffer)) {
const error = new Error('Signature verification failed.');
error.statusCode = 401;
throw error;
}

const claimedSigner = signerAddress || address;
if (!StrKey.isValidEd25519PublicKey(claimedSigner)) {
const error = new Error('Invalid signer address format.');
error.statusCode = 400;
throw error;
}

return claimedSigner;
};

/**
* Registration endpoint with multi-signer threshold verification
*
Expand All @@ -351,6 +385,7 @@ app.post('/register', async (req, res, next) => {
const memoType = typeof req.body.memo_type === 'string' ? req.body.memo_type.trim() : undefined;
const memo = typeof req.body.memo === 'string' ? req.body.memo.trim() : undefined;
const signature = typeof req.body.signature === 'string' ? req.body.signature.trim() : '';
const signerAddress = typeof req.body.signerAddress === 'string' ? req.body.signerAddress.trim() : '';

if (address.toUpperCase().startsWith('S')) {
return res.status(400).json({ error: "Never share your Secret Key. Please register using your Public Key (starts with G)." });
Expand Down Expand Up @@ -383,14 +418,6 @@ app.post('/register', async (req, res, next) => {
return res.status(400).json({ error: memoError });
}

// Signature is optional for legacy single-signer registrations.
// If provided, validate its format and run multi-signer verification.
if (signature && !StrKey.isValidEd25519PublicKey(signature)) {
const error = new Error('Invalid Stellar Public Key format.');
error.statusCode = 400;
return next(error);
}

const normalizedUsername = username.toLowerCase();

const RESERVED_NAMES = ['admin', 'root', 'support', 'system', 'stellar', 'api', 'help'];
Expand All @@ -417,18 +444,43 @@ app.post('/register', async (req, res, next) => {
conflictError.statusCode = 409;
return next(conflictError);
}

let verificationResult = null;
if (signature) {
verificationResult = await verifyMultiSignerThreshold(address, [signature], {
operationType: 'management',
});
const isLegacyPublicKeyFlow =
StrKey.isValidEd25519PublicKey(signature) && !signerAddress;

if (!verificationResult.success) {
const verificationError = new Error(
verificationResult.errorMessage || 'Signature verification failed'
);
verificationError.statusCode = 401;
throw verificationError;
if (isLegacyPublicKeyFlow) {
verificationResult = await verifyMultiSignerThreshold(address, [signature], {
operationType: 'management',
});

if (!verificationResult.success) {
const verificationError = new Error(
verificationResult.errorMessage || 'Signature verification failed'
);
verificationError.statusCode = 401;
throw verificationError;
}
} else {
const claimedSigner = verifyFreighterRegistrationSignature({
username: normalizedUsername,
address,
signature,
signerAddress,
});

verificationResult = await verifyMultiSignerThreshold(address, [claimedSigner], {
operationType: 'management',
});

if (!verificationResult.success) {
const verificationError = new Error(
verificationResult.errorMessage || 'Signature verification failed'
);
verificationError.statusCode = 401;
throw verificationError;
}
}
}

Expand Down
Loading