Implement payments for app deployments #17

Merged
nabarun merged 27 commits from iv-integrate-payments into main 2024-10-28 09:46:19 +00:00
6 changed files with 38 additions and 182 deletions
Showing only changes of commit aff015794e - Show all commits

View File

@ -41,6 +41,3 @@
revealFee = "100000" revealFee = "100000"
revealsDuration = "120s" revealsDuration = "120s"
denom = "alnt" denom = "alnt"
[misc]
projectDomain = "apps.snowballtools.com"

View File

@ -51,21 +51,10 @@ export interface AuctionConfig {
denom: string; denom: string;
} }
export interface MiscConfig {
projectDomain: string;
}
export interface Config { export interface Config {
server: ServerConfig; server: ServerConfig;
database: DatabaseConfig; database: DatabaseConfig;
gitHub: GitHubConfig; gitHub: GitHubConfig;
registryConfig: RegistryConfig; registryConfig: RegistryConfig;
auction: AuctionConfig; auction: AuctionConfig;
misc: MiscConfig;
turnkey: {
apiBaseUrl: string;
apiPublicKey: string;
apiPrivateKey: string;
defaultOrganizationId: string;
};
} }

View File

@ -13,7 +13,7 @@ import assert from 'assert';
import { customAlphabet } from 'nanoid'; import { customAlphabet } from 'nanoid';
import { lowercase, numbers } from 'nanoid-dictionary'; import { lowercase, numbers } from 'nanoid-dictionary';
import { DatabaseConfig, MiscConfig } from './config'; import { DatabaseConfig } from './config';
import { User } from './entity/User'; import { User } from './entity/User';
import { Organization } from './entity/Organization'; import { Organization } from './entity/Organization';
import { Project } from './entity/Project'; import { Project } from './entity/Project';

View File

@ -17,7 +17,7 @@ const log = debug('snowball:server');
const OAUTH_CLIENT_TYPE = 'oauth-app'; const OAUTH_CLIENT_TYPE = 'oauth-app';
export const main = async (): Promise<void> => { export const main = async (): Promise<void> => {
const { server, database, gitHub, registryConfig, misc } = await getConfig(); const { server, database, gitHub, registryConfig } = await getConfig();
const app = new OAuthApp({ const app = new OAuthApp({
clientType: OAUTH_CLIENT_TYPE, clientType: OAUTH_CLIENT_TYPE,

View File

@ -1,50 +1,50 @@
import { Router } from 'express'; import { Router } from 'express';
import { SiweMessage } from 'siwe'; import { SiweMessage } from 'siwe';
import { Service } from '../service'; import { Service } from '../service';
import { authenticateUser, createUser } from '../turnkey-backend'; // import { authenticateUser, createUser } from '../turnkey-backend';
const router = Router(); const router = Router();
// //
// Turnkey // Turnkey
// //
router.get('/registration/:email', async (req, res) => { // router.get('/registration/:email', async (req, res) => {
const service: Service = req.app.get('service'); // const service: Service = req.app.get('service');
const user = await service.getUserByEmail(req.params.email); // const user = await service.getUserByEmail(req.params.email);
if (user) { // if (user) {
return res.send({ subOrganizationId: user?.subOrgId }); // return res.send({ subOrganizationId: user?.subOrgId });
} else { // } else {
return res.sendStatus(204); // return res.sendStatus(204);
} // }
}); // });
router.post('/register', async (req, res) => { // router.post('/register', async (req, res) => {
console.log('Register', req.body); // console.log('Register', req.body);
const { email, challenge, attestation } = req.body; // const { email, challenge, attestation } = req.body;
const user = await createUser(req.app.get('service'), { // const user = await createUser(req.app.get('service'), {
challenge, // challenge,
attestation, // attestation,
userEmail: email, // userEmail: email,
userName: email.split('@')[0], // userName: email.split('@')[0],
}); // });
req.session.address = user.id; // req.session.address = user.id;
res.sendStatus(200); // res.sendStatus(200);
}); // });
router.post('/authenticate', async (req, res) => { // router.post('/authenticate', async (req, res) => {
console.log('Authenticate', req.body); // console.log('Authenticate', req.body);
const { signedWhoamiRequest } = req.body; // const { signedWhoamiRequest } = req.body;
const user = await authenticateUser( // const user = await authenticateUser(
req.app.get('service'), // req.app.get('service'),
signedWhoamiRequest, // signedWhoamiRequest,
); // );
if (user) { // if (user) {
req.session.address = user.id; // req.session.address = user.id;
res.sendStatus(200); // res.sendStatus(200);
} else { // } else {
res.sendStatus(401); // res.sendStatus(401);
} // }
}); // });
// //
// SIWE Auth // SIWE Auth

View File

@ -1,130 +0,0 @@
import { Turnkey, TurnkeyApiTypes } from '@turnkey/sdk-server';
// Default path for the first Ethereum address in a new HD wallet.
// See https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki, paths are in the form:
// m / purpose' / coin_type' / account' / change / address_index
// - Purpose is a constant set to 44' following the BIP43 recommendation.
// - Coin type is set to 60 (ETH) -- see https://github.com/satoshilabs/slips/blob/master/slip-0044.md
// - Account, Change, and Address Index are set to 0
import { DEFAULT_ETHEREUM_ACCOUNTS } from '@turnkey/sdk-server';
import { getConfig } from './utils';
import { Service } from './service';
type TAttestation = TurnkeyApiTypes['v1Attestation'];
type CreateUserParams = {
userName: string;
userEmail: string;
challenge: string;
attestation: TAttestation;
};
export async function createUser(
service: Service,
{ userName, userEmail, challenge, attestation }: CreateUserParams,
) {
try {
if (await service.getUserByEmail(userEmail)) {
throw new Error(`User already exists: ${userEmail}`);
}
const config = await getConfig();
const turnkey = new Turnkey(config.turnkey);
const apiClient = turnkey.api();
const walletName = `Default ETH Wallet`;
const createSubOrgResponse = await apiClient.createSubOrganization({
subOrganizationName: `Default SubOrg for ${userEmail}`,
rootQuorumThreshold: 1,
rootUsers: [
{
userName,
userEmail,
apiKeys: [],
authenticators: [
{
authenticatorName: 'Passkey',
challenge,
attestation,
},
],
},
],
wallet: {
walletName: walletName,
accounts: DEFAULT_ETHEREUM_ACCOUNTS,
},
});
const subOrgId = refineNonNull(createSubOrgResponse.subOrganizationId);
const wallet = refineNonNull(createSubOrgResponse.wallet);
const result = {
id: wallet.walletId,
address: wallet.addresses[0],
subOrgId: subOrgId,
};
console.log('Turnkey success', result);
const user = await service.createUser({
name: userName,
email: userEmail,
subOrgId,
ethAddress: wallet.addresses[0],
turnkeyWalletId: wallet.walletId,
});
console.log('New user', user);
return user;
} catch (e) {
console.error('Failed to create user:', e);
throw e;
}
}
export async function authenticateUser(
service: Service,
signedWhoamiRequest: {
url: string;
body: any;
stamp: {
stampHeaderName: string;
stampHeaderValue: string;
};
},
) {
try {
const tkRes = await fetch(signedWhoamiRequest.url, {
method: 'POST',
body: signedWhoamiRequest.body,
headers: {
[signedWhoamiRequest.stamp.stampHeaderName]:
signedWhoamiRequest.stamp.stampHeaderValue,
},
});
console.log('AUTH RESULT', tkRes.status);
if (tkRes.status !== 200) {
console.log(await tkRes.text());
return null;
}
const orgId = (await tkRes.json()).organizationId;
const user = await service.getUserBySubOrgId(orgId);
return user;
} catch (e) {
console.error('Failed to authenticate:', e);
throw e;
}
}
function refineNonNull<T>(
input: T | null | undefined,
errorMessage?: string,
): T {
if (input == null) {
throw new Error(errorMessage ?? `Unexpected ${JSON.stringify(input)}`);
}
return input;
}