73 lines
2.0 KiB
TypeScript
73 lines
2.0 KiB
TypeScript
import { pathToFileURL } from 'node:url'
|
|
|
|
import {
|
|
issueOperatorPasswordResetToken,
|
|
TokenDigester,
|
|
type IssueOperatorPasswordResetRequest,
|
|
type IssuedPasswordReset,
|
|
} from '@devrunbook/application'
|
|
import { closeDatabase, DrizzlePasswordResetStore } from '@devrunbook/db'
|
|
|
|
type IssueReset = (
|
|
request: IssueOperatorPasswordResetRequest,
|
|
) => Promise<IssuedPasswordReset>
|
|
|
|
export async function runOperatorPasswordReset(
|
|
arguments_: readonly string[],
|
|
environment: Record<string, string | undefined>,
|
|
writeOutput: (value: string) => void,
|
|
issueReset?: IssueReset,
|
|
): Promise<void> {
|
|
if (arguments_.length !== 1 || !arguments_[0]) {
|
|
throw new Error(
|
|
'Usage: pnpm --filter @devrunbook/worker operator:password-reset -- <user-email>',
|
|
)
|
|
}
|
|
const sessionSecret = environment.SESSION_SECRET
|
|
const publicBaseUrl = environment.PUBLIC_BASE_URL
|
|
if (!sessionSecret || sessionSecret.length < 32) {
|
|
throw new Error('SESSION_SECRET must contain at least 32 characters')
|
|
}
|
|
if (!publicBaseUrl) throw new Error('PUBLIC_BASE_URL is required')
|
|
|
|
const issue =
|
|
issueReset ??
|
|
((request) =>
|
|
issueOperatorPasswordResetToken(
|
|
{
|
|
store: new DrizzlePasswordResetStore(),
|
|
digester: new TokenDigester(Buffer.from(sessionSecret, 'utf8')),
|
|
},
|
|
request,
|
|
))
|
|
const result = await issue({
|
|
email: arguments_[0],
|
|
publicBaseUrl,
|
|
})
|
|
|
|
// Successful stdout is deliberately one line containing only the reset URL.
|
|
writeOutput(result.resetUrl)
|
|
}
|
|
|
|
async function main() {
|
|
try {
|
|
await runOperatorPasswordReset(
|
|
process.argv.slice(2),
|
|
process.env,
|
|
(value) => console.log(value),
|
|
)
|
|
} catch (error) {
|
|
const message =
|
|
error instanceof Error ? error.message : 'Password reset failed'
|
|
console.error(message)
|
|
process.exitCode = 1
|
|
} finally {
|
|
await closeDatabase()
|
|
}
|
|
}
|
|
|
|
const entryPoint = process.argv[1]
|
|
if (entryPoint && import.meta.url === pathToFileURL(entryPoint).href) {
|
|
await main()
|
|
}
|