-
Notifications
You must be signed in to change notification settings - Fork 44
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #10260 from hicommonwealth/rotorsoft/10214-create-…
…address Refactors create and verify address flows
- Loading branch information
Showing
37 changed files
with
1,120 additions
and
1,111 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -20,11 +20,12 @@ type Metadata<Input extends ZodSchema, Output extends ZodSchema> = { | |
readonly output: Output; | ||
auth: unknown[]; | ||
secure?: boolean; | ||
authStrategy?: AuthStrategies; | ||
authStrategy?: AuthStrategies<Input>; | ||
}; | ||
|
||
const isSecure = (md: Metadata<ZodSchema, ZodSchema>) => | ||
md.secure !== false || (md.auth ?? []).length > 0; | ||
const isSecure = <Input extends ZodSchema, Output extends ZodSchema>( | ||
md: Metadata<Input, Output>, | ||
) => md.secure !== false || (md.auth ?? []).length > 0; | ||
|
||
export interface Context { | ||
req: Request; | ||
|
@@ -151,6 +152,47 @@ export type BuildProcOptions< | |
forceSecure?: boolean; | ||
}; | ||
|
||
const authenticate = async <Input extends ZodSchema>( | ||
req: Request, | ||
rawInput: z.infer<Input>, | ||
authStrategy: AuthStrategies<Input> = { type: 'jwt' }, | ||
) => { | ||
// Bypass when user is already authenticated via JWT or token | ||
// Authentication overridden at router level e.g. external-router.ts | ||
if (req.user && authStrategy.type !== 'custom') return; | ||
|
||
try { | ||
if (authStrategy.type === 'authtoken') { | ||
switch (req.headers['authorization']) { | ||
case config.NOTIFICATIONS.KNOCK_AUTH_TOKEN: | ||
req.user = { | ||
id: authStrategy.userId, | ||
email: '[email protected]', | ||
}; | ||
break; | ||
case config.LOAD_TESTING.AUTH_TOKEN: | ||
req.user = { | ||
id: authStrategy.userId, | ||
email: '[email protected]', | ||
}; | ||
break; | ||
default: | ||
throw new Error('Not authenticated'); | ||
} | ||
} else if (authStrategy.type === 'custom') { | ||
req.user = await authStrategy.userResolver(rawInput, req.user as User); | ||
} else { | ||
await passport.authenticate(authStrategy.type, { session: false }); | ||
} | ||
if (!req.user) throw new Error('Not authenticated'); | ||
} catch (error) { | ||
throw new TRPCError({ | ||
message: error instanceof Error ? error.message : (error as string), | ||
code: 'UNAUTHORIZED', | ||
}); | ||
} | ||
}; | ||
|
||
/** | ||
* tRPC procedure factory with authentication, traffic stats, and analytics middleware | ||
*/ | ||
|
@@ -165,8 +207,8 @@ export const buildproc = <Input extends ZodSchema, Output extends ZodSchema>({ | |
}: BuildProcOptions<Input, Output>) => { | ||
const secure = forceSecure ?? isSecure(md); | ||
return trpc.procedure | ||
.use(async ({ ctx, next }) => { | ||
if (secure) await authenticate(ctx.req, md.authStrategy); | ||
.use(async ({ ctx, rawInput, next }) => { | ||
if (secure) await authenticate(ctx.req, rawInput, md.authStrategy); | ||
return next({ | ||
ctx: { | ||
...ctx, | ||
|
@@ -181,6 +223,26 @@ export const buildproc = <Input extends ZodSchema, Output extends ZodSchema>({ | |
const start = Date.now(); | ||
const result = await next(); | ||
const latency = Date.now() - start; | ||
|
||
// TODO: this is a Friday night hack, let's rethink output middleware | ||
if ( | ||
md.authStrategy?.type === 'custom' && | ||
md.authStrategy?.name === 'SignIn' && | ||
result.ok && | ||
result.data | ||
) { | ||
const data = result.data as z.infer<typeof md.output>; | ||
await new Promise((resolve, reject) => { | ||
ctx.req.login(data.User, (err) => { | ||
if (err) { | ||
// TODO: track Mixpanel login failure | ||
reject(err); | ||
} | ||
resolve(true); | ||
}); | ||
}); | ||
} | ||
|
||
try { | ||
const path = `${ctx.req.method.toUpperCase()} ${ctx.req.path}`; | ||
stats().increment('cw.path.called', { path }); | ||
|
@@ -218,49 +280,3 @@ export const buildproc = <Input extends ZodSchema, Output extends ZodSchema>({ | |
.input(md.input) | ||
.output(md.output); | ||
}; | ||
|
||
const authenticate = async ( | ||
req: Request, | ||
authStrategy: AuthStrategies = { name: 'jwt' }, | ||
) => { | ||
// User is already authenticated. Authentication overridden at router level e.g. external-router.ts | ||
if (req.user) return; | ||
|
||
try { | ||
if (authStrategy.name === 'authtoken') { | ||
switch (req.headers['authorization']) { | ||
case config.NOTIFICATIONS.KNOCK_AUTH_TOKEN: | ||
req.user = { | ||
id: authStrategy.userId, | ||
email: '[email protected]', | ||
}; | ||
break; | ||
case config.LOAD_TESTING.AUTH_TOKEN: | ||
req.user = { | ||
id: authStrategy.userId, | ||
email: '[email protected]', | ||
}; | ||
break; | ||
default: | ||
throw new Error('Not authenticated'); | ||
} | ||
} else if (authStrategy.name === 'custom') { | ||
authStrategy.customStrategyFn(req); | ||
req.user = { | ||
id: authStrategy.userId, | ||
}; | ||
} else { | ||
await passport.authenticate(authStrategy.name, { session: false }); | ||
} | ||
|
||
if (!req.user) throw new Error('Not authenticated'); | ||
if (authStrategy.userId && (req.user as User).id !== authStrategy.userId) { | ||
throw new Error('Not authenticated'); | ||
} | ||
} catch (error) { | ||
throw new TRPCError({ | ||
message: error instanceof Error ? error.message : (error as string), | ||
code: 'UNAUTHORIZED', | ||
}); | ||
} | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
7 changes: 2 additions & 5 deletions
7
...lth/server/util/assertAddressOwnership.ts → ...ervices/session/assertAddressOwnership.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
export * from './assertAddressOwnership'; | ||
export * from './transferOwnership'; | ||
export * from './verifyAddress'; | ||
export * from './verifySessionSignature'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
import * as schemas from '@hicommonwealth/schemas'; | ||
import { Op, Transaction } from 'sequelize'; | ||
import { z } from 'zod'; | ||
import { models } from '../../database'; | ||
|
||
/** | ||
* Transfers ownership of address to user in addr from other users with same address | ||
*/ | ||
export async function transferOwnership( | ||
addr: z.infer<typeof schemas.Address>, | ||
transaction: Transaction, | ||
) { | ||
const found = await models.Address.findOne({ | ||
where: { | ||
address: addr.address, | ||
user_id: { [Op.ne]: addr.user_id }, | ||
// verified: { [Op.ne]: null }, | ||
}, | ||
include: { | ||
model: models.User, | ||
required: true, | ||
attributes: ['id', 'email'], | ||
}, | ||
transaction, | ||
}); | ||
if (found) { | ||
const [updated] = await models.Address.update( | ||
{ user_id: addr.user_id }, | ||
{ | ||
where: { address: addr.address, user_id: found?.user_id }, | ||
transaction, | ||
}, | ||
); | ||
if (updated > 0) return found?.User; | ||
} | ||
} | ||
|
||
// TODO: subscribe to AddressOwnershipTransferred event | ||
// if (updated > 0 && unverifed) { | ||
// try { | ||
// // send email to the old user (should only ever be one) | ||
// if (!unverifed.User?.email) throw new InvalidState(Errors.NoEmail); | ||
|
||
// const msg = { | ||
// to: unverifed.User.email, | ||
// from: `Commonwealth <no-reply@${PRODUCTION_DOMAIN}>`, | ||
// templateId: DynamicTemplate.VerifyAddress, | ||
// dynamic_template_data: { | ||
// address: addr.address, | ||
// chain: community.name, | ||
// }, | ||
// }; | ||
// await sgMail.send(msg); | ||
// log.info( | ||
// `Sent address move email: ${addr.address} transferred to a new account`, | ||
// ); | ||
// } catch (e) { | ||
// log.error( | ||
// `Could not send address move email for: ${addr.address}`, | ||
// e as Error, | ||
// ); | ||
// } | ||
// } |
Oops, something went wrong.