Implement payments for app deployments #17
@ -41,6 +41,3 @@
|
|||||||
revealFee = "100000"
|
revealFee = "100000"
|
||||||
revealsDuration = "120s"
|
revealsDuration = "120s"
|
||||||
denom = "alnt"
|
denom = "alnt"
|
||||||
|
|
||||||
[misc]
|
|
||||||
projectDomain = "apps.snowballtools.com"
|
|
||||||
|
@ -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;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
@ -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';
|
||||||
|
@ -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,
|
||||||
|
@ -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
|
||||||
|
@ -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;
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user