Skip to content

Commit

Permalink
Forgot password methods (bluesky-social#216)
Browse files Browse the repository at this point in the history
* Schemas and scaffolding for reset password methods

* Initial handler for todo.adx.requestAccountPasswordReset

* Initial handler for todo.adx.resetAccountPassword

* Implement server mailer

* Configure server for mailer and testing w/ mailer

* Test happy path of pass reset, fix reset bug

* Update lex to fix types bug for requestAccountPasswordReset

* Fix handlebars reference to config getters

* Test some negative password reset flows

* Minor cleanup to pass reset

* Tidy handlebars file with prettier, supporting double-quotes for html

* Fix esbuild of server for mailer templates, fix test issue

* Misc tidying for password reset

* Misc tidying for password reset
  • Loading branch information
devinivy authored Oct 6, 2022
1 parent 904fa64 commit 59f18ac
Show file tree
Hide file tree
Showing 30 changed files with 966 additions and 95 deletions.
10 changes: 9 additions & 1 deletion .prettierrc
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,13 @@
"trailingComma": "all",
"tabWidth": 2,
"semi": false,
"singleQuote": true
"singleQuote": true,
"overrides": [
{
"files": "*.hbs",
"options": {
"singleQuote": false
}
}
]
}
28 changes: 28 additions & 0 deletions packages/api/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ import * as TodoAdxRepoDescribe from './types/todo/adx/repoDescribe'
import * as TodoAdxRepoGetRecord from './types/todo/adx/repoGetRecord'
import * as TodoAdxRepoListRecords from './types/todo/adx/repoListRecords'
import * as TodoAdxRepoPutRecord from './types/todo/adx/repoPutRecord'
import * as TodoAdxRequestAccountPasswordReset from './types/todo/adx/requestAccountPasswordReset'
import * as TodoAdxResetAccountPassword from './types/todo/adx/resetAccountPassword'
import * as TodoAdxResolveName from './types/todo/adx/resolveName'
import * as TodoAdxSyncGetRepo from './types/todo/adx/syncGetRepo'
import * as TodoAdxSyncGetRoot from './types/todo/adx/syncGetRoot'
Expand Down Expand Up @@ -59,6 +61,8 @@ export * as TodoAdxRepoDescribe from './types/todo/adx/repoDescribe'
export * as TodoAdxRepoGetRecord from './types/todo/adx/repoGetRecord'
export * as TodoAdxRepoListRecords from './types/todo/adx/repoListRecords'
export * as TodoAdxRepoPutRecord from './types/todo/adx/repoPutRecord'
export * as TodoAdxRequestAccountPasswordReset from './types/todo/adx/requestAccountPasswordReset'
export * as TodoAdxResetAccountPassword from './types/todo/adx/resetAccountPassword'
export * as TodoAdxResolveName from './types/todo/adx/resolveName'
export * as TodoAdxSyncGetRepo from './types/todo/adx/syncGetRepo'
export * as TodoAdxSyncGetRoot from './types/todo/adx/syncGetRoot'
Expand Down Expand Up @@ -312,6 +316,30 @@ export class AdxNS {
})
}

requestAccountPasswordReset(
params: TodoAdxRequestAccountPasswordReset.QueryParams,
data?: TodoAdxRequestAccountPasswordReset.InputSchema,
opts?: TodoAdxRequestAccountPasswordReset.CallOptions
): Promise<TodoAdxRequestAccountPasswordReset.Response> {
return this._service.xrpc
.call('todo.adx.requestAccountPasswordReset', params, data, opts)
.catch((e) => {
throw TodoAdxRequestAccountPasswordReset.toKnownErr(e)
})
}

resetAccountPassword(
params: TodoAdxResetAccountPassword.QueryParams,
data?: TodoAdxResetAccountPassword.InputSchema,
opts?: TodoAdxResetAccountPassword.CallOptions
): Promise<TodoAdxResetAccountPassword.Response> {
return this._service.xrpc
.call('todo.adx.resetAccountPassword', params, data, opts)
.catch((e) => {
throw TodoAdxResetAccountPassword.toKnownErr(e)
})
}

resolveName(
params: TodoAdxResolveName.QueryParams,
data?: TodoAdxResolveName.InputSchema,
Expand Down
63 changes: 63 additions & 0 deletions packages/api/src/schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -559,6 +559,69 @@ export const methodSchemas: MethodSchema[] = [
},
},
},
{
lexicon: 1,
id: 'todo.adx.requestAccountPasswordReset',
type: 'procedure',
description: 'Initiate a user account password reset via email',
parameters: {},
input: {
encoding: 'application/json',
schema: {
type: 'object',
required: ['email'],
properties: {
email: {
type: 'string',
},
},
},
},
output: {
encoding: 'application/json',
schema: {
type: 'object',
properties: {},
},
},
},
{
lexicon: 1,
id: 'todo.adx.resetAccountPassword',
type: 'procedure',
description: 'Reset a user account password using a token',
parameters: {},
input: {
encoding: 'application/json',
schema: {
type: 'object',
required: ['token', 'password'],
properties: {
token: {
type: 'string',
},
password: {
type: 'string',
},
},
},
},
output: {
encoding: 'application/json',
schema: {
type: 'object',
properties: {},
},
},
errors: [
{
name: 'ExpiredToken',
},
{
name: 'InvalidToken',
},
],
},
{
lexicon: 1,
id: 'todo.adx.resolveName',
Expand Down
29 changes: 29 additions & 0 deletions packages/api/src/types/todo/adx/requestAccountPasswordReset.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/**
* GENERATED CODE - DO NOT MODIFY
*/
import { Headers, XRPCError } from '@adxp/xrpc'

export interface QueryParams {}

export interface CallOptions {
headers?: Headers;
encoding: 'application/json';
}

export interface InputSchema {
email: string;
}

export interface OutputSchema {}

export interface Response {
success: boolean;
headers: Headers;
data: OutputSchema;
}

export function toKnownErr(e: any) {
if (e instanceof XRPCError) {
}
return e
}
44 changes: 44 additions & 0 deletions packages/api/src/types/todo/adx/resetAccountPassword.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/**
* GENERATED CODE - DO NOT MODIFY
*/
import { Headers, XRPCError } from '@adxp/xrpc'

export interface QueryParams {}

export interface CallOptions {
headers?: Headers;
encoding: 'application/json';
}

export interface InputSchema {
token: string;
password: string;
}

export interface OutputSchema {}

export interface Response {
success: boolean;
headers: Headers;
data: OutputSchema;
}

export class ExpiredTokenError extends XRPCError {
constructor(src: XRPCError) {
super(src.status, src.error, src.message)
}
}

export class InvalidTokenError extends XRPCError {
constructor(src: XRPCError) {
super(src.status, src.error, src.message)
}
}

export function toKnownErr(e: any) {
if (e instanceof XRPCError) {
if (e.error === 'ExpiredToken') return new ExpiredTokenError(e)
if (e.error === 'InvalidToken') return new InvalidTokenError(e)
}
return e
}
7 changes: 6 additions & 1 deletion packages/dev-env/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,12 @@ export class DevEnvServer {
serverDid: serverDid,
testNameRegistry: this.env.testNameRegistry,
jwtSecret: crytpo.randomBytes(8).toString('base64'),
}),
appUrlPasswordReset: 'app://password-reset',
// @TODO setup ethereal.email creds and set emailSmtpUrl here
emailNoReplyAddress: '[email protected]',
adminPassword: 'password',
inviteRequired: false,
}).listener,
)
break
}
Expand Down
6 changes: 4 additions & 2 deletions packages/lex-cli/src/codegen/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,8 +164,10 @@ function genNamespaceCls(file: SourceFile, ns: NsidNS) {
})
method.setBodyText(
[
`/** @ts-ignore */`,
`return this.server.xrpc.method('${schema.id}', handler)`,
// Placing schema on separate line, since the following one was being formatted
// into multiple lines and causing the ts-ignore to ignore the wrong line.
`const schema = '${schema.id}' // @ts-ignore`,
`return this.server.xrpc.method(schema, handler)`,
].join('\n'),
)
}
Expand Down
13 changes: 13 additions & 0 deletions packages/server/build.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
const { copy } = require('esbuild-plugin-copy')

require('esbuild')
.build({
logLevel: 'info',
Expand All @@ -13,5 +15,16 @@ require('esbuild')
'../../node_modules/level/*',
'../../node_modules/classic-level/*',
],
plugins: [
copy({
// this is equal to process.cwd(), which means we use cwd path as base path to resolve `to` path
// if not specified, this plugin uses ESBuild.build outdir/outfile options as base path.
assets: {
from: ['./src/mailer/templates/**/*'],
to: ['./templates'],
keepStructure: true,
},
}),
],
})
.catch(() => process.exit(1))
5 changes: 5 additions & 0 deletions packages/server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,11 @@
"dotenv": "^16.0.0",
"express": "^4.17.2",
"express-async-errors": "^3.1.1",
"handlebars": "^4.7.7",
"jsonwebtoken": "^8.5.1",
"level": "^8.0.0",
"nodemailer": "^6.8.0",
"nodemailer-html-to-text": "^3.2.0",
"pino-http": "^8.2.1",
"sqlite3": "^5.0.11",
"typeorm": "^0.3.7",
Expand All @@ -42,7 +45,9 @@
"@types/cors": "^2.8.12",
"@types/express": "^4.17.13",
"@types/jsonwebtoken": "^8.5.9",
"@types/nodemailer": "^6.4.6",
"axios": "^0.26.1",
"esbuild-plugin-copy": "^1.3.0",
"get-port": "^6.1.2",
"nodemon": "^2.0.15",
"rimraf": "^3.0.2"
Expand Down
4 changes: 2 additions & 2 deletions packages/server/src/api/todo/adx/account.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { Server } from '../../../lexicon'
import { InvalidRequestError } from '@adxp/xrpc-server'
import * as locals from '../../../locals'
import { Repo } from '@adxp/repo'
import { PlcClient } from '@adxp/plc'
import { Server } from '../../../lexicon'
import * as locals from '../../../locals'
import { InviteCode, InviteCodeUse } from '../../../db/invite-codes'

export default function (server: Server) {
Expand Down
2 changes: 2 additions & 0 deletions packages/server/src/api/todo/adx/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Server } from '../../../lexicon'
import names from './names'
import session from './session'
import account from './account'
import passwordReset from './password-reset'
import repo from './repo'
import sync from './sync'
import invites from './invites'
Expand All @@ -10,6 +11,7 @@ export default function (server: Server) {
names(server)
session(server)
account(server)
passwordReset(server)
repo(server)
sync(server)
invites(server)
Expand Down
Loading

0 comments on commit 59f18ac

Please sign in to comment.