mirror of
https://github.com/mollersuite/monofile.git
synced 2024-11-25 07:06:25 -08:00
identity proofs
This commit is contained in:
parent
8f13bf2fea
commit
9b68d7a705
|
@ -1,15 +1,22 @@
|
||||||
import { generateFileId } from "./files.js";
|
import { generateFileId } from "./files.js";
|
||||||
|
import crypto from "node:crypto"
|
||||||
|
|
||||||
export const Intents = ["verifyEmail", "recoverAccount", "deletionOtp"] as const
|
export type Intent = "verifyEmail" | "recoverAccount" | "identityProof"
|
||||||
|
|
||||||
export type Intent = (typeof Intents)[number]
|
export const Intents = {
|
||||||
|
verifyEmail: {},
|
||||||
|
recoverAccount: {},
|
||||||
|
identityProof: {
|
||||||
|
codeGenerator: crypto.randomUUID
|
||||||
|
}
|
||||||
|
} as Record<Intent, {codeGenerator?: () => string}>
|
||||||
|
|
||||||
export function isIntent(intent: string): intent is Intent {
|
export function isIntent(intent: string): intent is Intent {
|
||||||
return intent in Intents
|
return intent in Intents
|
||||||
}
|
}
|
||||||
|
|
||||||
export let codes = Object.fromEntries(
|
export let codes = Object.fromEntries(
|
||||||
Intents.map((e) => [
|
Object.keys(Intents).map((e) => [
|
||||||
e,
|
e,
|
||||||
{
|
{
|
||||||
byId: new Map<string, Code>(),
|
byId: new Map<string, Code>(),
|
||||||
|
@ -24,13 +31,10 @@ export let codes = Object.fromEntries(
|
||||||
// this is stupid whyd i write this
|
// this is stupid whyd i write this
|
||||||
|
|
||||||
export class Code {
|
export class Code {
|
||||||
readonly id: string = generateFileId(12)
|
readonly id: string
|
||||||
readonly for: string
|
readonly for: string
|
||||||
|
|
||||||
readonly intent: Intent
|
readonly intent: Intent
|
||||||
|
|
||||||
readonly expiryClear: NodeJS.Timeout
|
readonly expiryClear: NodeJS.Timeout
|
||||||
|
|
||||||
readonly data: any
|
readonly data: any
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
@ -39,25 +43,28 @@ export class Code {
|
||||||
data?: any,
|
data?: any,
|
||||||
time: number = 15 * 60 * 1000
|
time: number = 15 * 60 * 1000
|
||||||
) {
|
) {
|
||||||
|
const { codeGenerator = () => generateFileId(12) } = Intents[intent]
|
||||||
|
|
||||||
this.for = forUser
|
this.for = forUser
|
||||||
this.intent = intent
|
this.intent = intent
|
||||||
this.expiryClear = setTimeout(this.terminate.bind(this), time)
|
this.expiryClear = setTimeout(this.terminate.bind(this), time)
|
||||||
this.data = data
|
this.data = data
|
||||||
|
this.id = codeGenerator()
|
||||||
|
|
||||||
codes[intent].byId.set(this.id, this)
|
let byUser = codes[intent].byUser.get(forUser)
|
||||||
|
|
||||||
let byUser = codes[intent].byUser.get(this.for)
|
|
||||||
if (!byUser) {
|
if (!byUser) {
|
||||||
byUser = []
|
byUser = []
|
||||||
codes[intent].byUser.set(this.for, byUser)
|
codes[intent].byUser.set(forUser, byUser)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
codes[intent].byId.set(this.id, this)
|
||||||
|
|
||||||
byUser.push(this)
|
byUser.push(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
terminate() {
|
terminate() {
|
||||||
codes[this.intent].byId.delete(this.id)
|
codes[this.intent].byId.delete(this.id)
|
||||||
let bu = codes[this.intent].byUser.get(this.id)!
|
let bu = codes[this.intent].byUser.get(this.for)!
|
||||||
bu.splice(bu.indexOf(this), 1)
|
bu.splice(bu.indexOf(this), 1)
|
||||||
clearTimeout(this.expiryClear)
|
clearTimeout(this.expiryClear)
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import ServeError from "../lib/errors.js"
|
||||||
import * as auth from "./auth.js"
|
import * as auth from "./auth.js"
|
||||||
import { setCookie } from "hono/cookie"
|
import { setCookie } from "hono/cookie"
|
||||||
import { z } from "zod"
|
import { z } from "zod"
|
||||||
|
import { codes } from "./codes.js"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description Middleware which adds an account, if any, to ctx.get("account")
|
* @description Middleware which adds an account, if any, to ctx.get("account")
|
||||||
|
@ -21,17 +22,55 @@ export const getAccount: RequestHandler = function (ctx, next) {
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export const getTarget: RequestHandler = async (ctx, next) => {
|
export const getTarget: RequestHandler = async (ctx, next) => {
|
||||||
let acc =
|
let tok = auth.tokenFor(ctx)
|
||||||
|
let permissions
|
||||||
|
if (tok && auth.getType(tok) != "User")
|
||||||
|
permissions = auth.getPermissions(tok)
|
||||||
|
|
||||||
|
let actor = ctx.get("account")
|
||||||
|
|
||||||
|
let target =
|
||||||
ctx.req.param("user") == "me"
|
ctx.req.param("user") == "me"
|
||||||
? ctx.get("account")
|
? actor
|
||||||
: ctx.req.param("user").startsWith("@")
|
: ctx.req.param("user").startsWith("@")
|
||||||
? Accounts.getFromUsername(ctx.req.param("user").slice(1))
|
? Accounts.getFromUsername(ctx.req.param("user").slice(1))
|
||||||
: Accounts.getFromId(ctx.req.param("user"))
|
: Accounts.getFromId(ctx.req.param("user"))
|
||||||
if (acc != ctx.get("account") && !ctx.get("account")?.admin)
|
|
||||||
return ServeError(ctx, 403, "you cannot manage this user")
|
|
||||||
if (!acc) return ServeError(ctx, 404, "account does not exist")
|
|
||||||
|
|
||||||
ctx.set("target", acc)
|
if (!target) return ServeError(ctx, 404, "account does not exist")
|
||||||
|
|
||||||
|
if (actor && (
|
||||||
|
(
|
||||||
|
target != actor // target is not the current account
|
||||||
|
&& !actor?.admin // account is not admin
|
||||||
|
)
|
||||||
|
|| (
|
||||||
|
actor?.admin // account is admin
|
||||||
|
&& permissions && !permissions.includes("manage_server") // permissions does not include manage_server
|
||||||
|
)
|
||||||
|
))
|
||||||
|
return ServeError(ctx, 403, "you cannot manage this user")
|
||||||
|
|
||||||
|
ctx.set("target", target)
|
||||||
|
|
||||||
|
return next()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description Blocks routes with a target user set to the account performing the action from bot tokens which do not have the manage_account permission
|
||||||
|
*/
|
||||||
|
export const accountMgmtRoute: RequestHandler = async (ctx,next) => {
|
||||||
|
let tok = auth.tokenFor(ctx)
|
||||||
|
let permissions
|
||||||
|
if (tok && auth.getType(tok) != "User")
|
||||||
|
permissions = auth.getPermissions(tok)
|
||||||
|
|
||||||
|
if (
|
||||||
|
(
|
||||||
|
ctx.get("account") == ctx.get("target") // if the current target is the user account
|
||||||
|
&& (permissions && !permissions.includes("manage_account")) // if permissions does not include manage_account
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return ServeError(ctx, 403, "you cannot manage this user")
|
||||||
|
|
||||||
return next()
|
return next()
|
||||||
}
|
}
|
||||||
|
@ -46,6 +85,16 @@ export const requiresAccount: RequestHandler = function (ctx, next) {
|
||||||
return next()
|
return next()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description Middleware which blocks requests which do not have ctx.get("target") set
|
||||||
|
*/
|
||||||
|
export const requiresTarget: RequestHandler = function (ctx, next) {
|
||||||
|
if (!ctx.get("target")) {
|
||||||
|
return ServeError(ctx, 404, "no target account")
|
||||||
|
}
|
||||||
|
return next()
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description Middleware which blocks requests that have ctx.get("account").admin set to a falsy value
|
* @description Middleware which blocks requests that have ctx.get("account").admin set to a falsy value
|
||||||
*/
|
*/
|
||||||
|
@ -135,10 +184,25 @@ export function scheme(scheme: z.ZodTypeAny, transformer: (ctx: Context) => Prom
|
||||||
|
|
||||||
// Not really middleware but a utility
|
// Not really middleware but a utility
|
||||||
|
|
||||||
export const login = (ctx: Context, account: string) =>
|
export const login = (ctx: Context, account: string) => {
|
||||||
setCookie(ctx, "auth", auth.create(account, 3 * 24 * 60 * 60 * 1000), {
|
let token = auth.create(account, 3 * 24 * 60 * 60 * 1000)
|
||||||
|
setCookie(ctx, "auth", token, {
|
||||||
path: "/",
|
path: "/",
|
||||||
sameSite: "Strict",
|
sameSite: "Strict",
|
||||||
secure: true,
|
secure: true,
|
||||||
httpOnly: true
|
httpOnly: true
|
||||||
})
|
})
|
||||||
|
return token
|
||||||
|
}
|
||||||
|
|
||||||
|
export const verifyPoi = (user: string, poi?: string, wantsMfaPoi: boolean = false) => {
|
||||||
|
if (!poi) return false
|
||||||
|
|
||||||
|
let poiCode = codes.identityProof.byId.get(poi)
|
||||||
|
|
||||||
|
if (!poiCode || poiCode.for !== user || poiCode.data == wantsMfaPoi)
|
||||||
|
return false
|
||||||
|
|
||||||
|
poiCode.terminate()
|
||||||
|
return true
|
||||||
|
}
|
|
@ -20,7 +20,7 @@ adminRoutes
|
||||||
.use(getAccount)
|
.use(getAccount)
|
||||||
.use(requiresAccount)
|
.use(requiresAccount)
|
||||||
.use(requiresAdmin)
|
.use(requiresAdmin)
|
||||||
.use(requiresPermissions("admin"))
|
.use(requiresPermissions("manage_server"))
|
||||||
|
|
||||||
export default function (files: Files) {
|
export default function (files: Files) {
|
||||||
adminRoutes.post("/reset", async (ctx) => {
|
adminRoutes.post("/reset", async (ctx) => {
|
||||||
|
|
|
@ -9,6 +9,7 @@ import Files from "../../../../lib/files.js"
|
||||||
import * as Accounts from "../../../../lib/accounts.js"
|
import * as Accounts from "../../../../lib/accounts.js"
|
||||||
import * as auth from "../../../../lib/auth.js"
|
import * as auth from "../../../../lib/auth.js"
|
||||||
import {
|
import {
|
||||||
|
accountMgmtRoute,
|
||||||
assertAPI,
|
assertAPI,
|
||||||
getAccount,
|
getAccount,
|
||||||
getTarget,
|
getTarget,
|
||||||
|
@ -18,6 +19,7 @@ import {
|
||||||
requiresAccount,
|
requiresAccount,
|
||||||
requiresPermissions,
|
requiresPermissions,
|
||||||
scheme,
|
scheme,
|
||||||
|
verifyPoi,
|
||||||
} from "../../../../lib/middleware.js"
|
} from "../../../../lib/middleware.js"
|
||||||
import ServeError from "../../../../lib/errors.js"
|
import ServeError from "../../../../lib/errors.js"
|
||||||
import { CodeMgr, sendMail } from "../../../../lib/mail.js"
|
import { CodeMgr, sendMail } from "../../../../lib/mail.js"
|
||||||
|
@ -36,7 +38,7 @@ const router = new Hono<{
|
||||||
type UserUpdateParameters = Partial<
|
type UserUpdateParameters = Partial<
|
||||||
Omit<Accounts.Account, "password"> & {
|
Omit<Accounts.Account, "password"> & {
|
||||||
password: string
|
password: string
|
||||||
currentPassword?: string
|
poi?: string
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
type Message = [200 | 400 | 401 | 403 | 429 | 501, string]
|
type Message = [200 | 400 | 401 | 403 | 429 | 501, string]
|
||||||
|
@ -69,7 +71,9 @@ type SchemedValidator<
|
||||||
T extends keyof Partial<Accounts.Account>
|
T extends keyof Partial<Accounts.Account>
|
||||||
> = {
|
> = {
|
||||||
validator: Validator<T>,
|
validator: Validator<T>,
|
||||||
schema: z.ZodTypeAny
|
schema: z.ZodTypeAny,
|
||||||
|
noAPIAccess?: boolean,
|
||||||
|
requireProofOfIdentity?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const validators: {
|
const validators: {
|
||||||
|
@ -83,13 +87,9 @@ const validators: {
|
||||||
},
|
},
|
||||||
email: {
|
email: {
|
||||||
schema: AccountSchemas.Account.shape.email.nullable(),
|
schema: AccountSchemas.Account.shape.email.nullable(),
|
||||||
|
noAPIAccess: true,
|
||||||
|
requireProofOfIdentity: true,
|
||||||
validator: (actor, target, params, ctx) => {
|
validator: (actor, target, params, ctx) => {
|
||||||
if (
|
|
||||||
!params.currentPassword || // actor on purpose here to allow admins
|
|
||||||
(params.currentPassword &&
|
|
||||||
Accounts.password.check(actor.id, params.currentPassword))
|
|
||||||
)
|
|
||||||
return [401, "current password incorrect"]
|
|
||||||
|
|
||||||
if (!params.email) {
|
if (!params.email) {
|
||||||
if (target.email) {
|
if (target.email) {
|
||||||
|
@ -107,8 +107,7 @@ const validators: {
|
||||||
// send verification email
|
// send verification email
|
||||||
|
|
||||||
if (
|
if (
|
||||||
(CodeMgr.codes.verifyEmail.byUser.get(target.id)?.length ||
|
(CodeMgr.codes.verifyEmail.byUser.get(target.id)?.length || 0) >= 2
|
||||||
0) >= 2
|
|
||||||
)
|
)
|
||||||
return [429, "you have too many active codes"]
|
return [429, "you have too many active codes"]
|
||||||
|
|
||||||
|
@ -135,14 +134,9 @@ const validators: {
|
||||||
},
|
},
|
||||||
password: {
|
password: {
|
||||||
schema: AccountSchemas.StringPassword,
|
schema: AccountSchemas.StringPassword,
|
||||||
|
noAPIAccess: true,
|
||||||
|
requireProofOfIdentity: true,
|
||||||
validator: (actor, target, params) => {
|
validator: (actor, target, params) => {
|
||||||
if (
|
|
||||||
!params.currentPassword || // actor on purpose here to allow admins
|
|
||||||
(params.currentPassword &&
|
|
||||||
Accounts.password.check(actor.id, params.currentPassword))
|
|
||||||
)
|
|
||||||
return [401, "current password incorrect"]
|
|
||||||
|
|
||||||
if (target.email) {
|
if (target.email) {
|
||||||
sendMail(
|
sendMail(
|
||||||
target.email,
|
target.email,
|
||||||
|
@ -158,14 +152,9 @@ const validators: {
|
||||||
},
|
},
|
||||||
username: {
|
username: {
|
||||||
schema: AccountSchemas.Username,
|
schema: AccountSchemas.Username,
|
||||||
|
noAPIAccess: true,
|
||||||
|
requireProofOfIdentity: true,
|
||||||
validator: (actor, target, params) => {
|
validator: (actor, target, params) => {
|
||||||
if (
|
|
||||||
!params.currentPassword || // actor on purpose here to allow admins
|
|
||||||
(params.currentPassword &&
|
|
||||||
Accounts.password.check(actor.id, params.currentPassword))
|
|
||||||
)
|
|
||||||
return [401, "current password incorrect"]
|
|
||||||
|
|
||||||
if (Accounts.getFromUsername(params.username))
|
if (Accounts.getFromUsername(params.username))
|
||||||
return [400, "account with this username already exists"]
|
return [400, "account with this username already exists"]
|
||||||
|
|
||||||
|
@ -217,7 +206,11 @@ const validators: {
|
||||||
}
|
}
|
||||||
|
|
||||||
router.use(getAccount)
|
router.use(getAccount)
|
||||||
router.all("/:user", getTarget)
|
router.on(
|
||||||
|
["GET","PATCH","DELETE"],
|
||||||
|
"/:user",
|
||||||
|
requiresAccount, getTarget, accountMgmtRoute
|
||||||
|
)
|
||||||
|
|
||||||
function isMessage(object: any): object is Message {
|
function isMessage(object: any): object is Message {
|
||||||
return (
|
return (
|
||||||
|
@ -228,17 +221,40 @@ function isMessage(object: any): object is Message {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Result = [
|
||||||
|
keyof Accounts.Account,
|
||||||
|
Accounts.Account[keyof Accounts.Account],
|
||||||
|
] | Message
|
||||||
|
|
||||||
|
const BaseUserUpdateScheme = z.object(
|
||||||
|
Object.fromEntries(Object.entries(validators).filter(e => !e[1].requireProofOfIdentity).map(
|
||||||
|
([name, validator]) => [name, validator.schema.optional()]
|
||||||
|
))
|
||||||
|
)
|
||||||
|
|
||||||
|
const UserUpdateScheme = z.union([
|
||||||
|
BaseUserUpdateScheme.extend({
|
||||||
|
poi: z.undefined()
|
||||||
|
}).strict(),
|
||||||
|
BaseUserUpdateScheme.extend({
|
||||||
|
poi: z.string().uuid(),
|
||||||
|
...Object.fromEntries(Object.entries(validators).filter(e => e[1].requireProofOfIdentity).map(
|
||||||
|
([name, validator]) => [name, validator.schema.optional()]
|
||||||
|
))
|
||||||
|
}).strict()
|
||||||
|
])
|
||||||
|
|
||||||
export default function (files: Files) {
|
export default function (files: Files) {
|
||||||
router.post("/", scheme(z.object({
|
router.post("/", scheme(z.object({
|
||||||
username: AccountSchemas.Username,
|
username: AccountSchemas.Username,
|
||||||
password: AccountSchemas.StringPassword
|
password: AccountSchemas.StringPassword
|
||||||
})), async (ctx) => {
|
})), async (ctx) => {
|
||||||
const body = await ctx.req.json()
|
const body = await ctx.req.json()
|
||||||
if (!Configuration.accounts.registrationEnabled) {
|
if (!ctx.get("account")?.admin) {
|
||||||
|
if (!Configuration.accounts.registrationEnabled)
|
||||||
return ServeError(ctx, 403, "account registration disabled")
|
return ServeError(ctx, 403, "account registration disabled")
|
||||||
}
|
|
||||||
|
|
||||||
if (auth.validate(getCookie(ctx, "auth")!)) {
|
if (ctx.get("account"))
|
||||||
return ServeError(ctx, 400, "you are already logged in")
|
return ServeError(ctx, 400, "you are already logged in")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -252,8 +268,9 @@ export default function (files: Files) {
|
||||||
|
|
||||||
return Accounts.create(body.username, body.password)
|
return Accounts.create(body.username, body.password)
|
||||||
.then((account) => {
|
.then((account) => {
|
||||||
|
if (!ctx.get("account"))
|
||||||
login(ctx, account)
|
login(ctx, account)
|
||||||
return ctx.text("logged in")
|
return ctx.text(account)
|
||||||
})
|
})
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
console.error(e)
|
console.error(e)
|
||||||
|
@ -263,39 +280,27 @@ export default function (files: Files) {
|
||||||
|
|
||||||
router.patch(
|
router.patch(
|
||||||
"/:user",
|
"/:user",
|
||||||
requiresAccount,
|
scheme(UserUpdateScheme),
|
||||||
requiresPermissions("manage_account"),
|
|
||||||
async (ctx) => {
|
async (ctx) => {
|
||||||
const body = (await ctx.req.json()) as UserUpdateParameters
|
const body = (await ctx.req.json()) as z.infer<typeof UserUpdateScheme>
|
||||||
const actor = ctx.get("account")!
|
const actor = ctx.get("account")!
|
||||||
const target = ctx.get("target")!
|
const target = ctx.get("target")!
|
||||||
if (Array.isArray(body)) return ServeError(ctx, 400, "invalid body")
|
const tokenType = auth.getType(auth.tokenFor(ctx)!)
|
||||||
|
|
||||||
let results: (
|
if (body.poi && !verifyPoi(target.id, body.poi))
|
||||||
| [
|
return ServeError(ctx, 403, "invalid proof of identity provided")
|
||||||
keyof Accounts.Account,
|
|
||||||
Accounts.Account[keyof Accounts.Account],
|
let results: Result[] = (
|
||||||
]
|
|
||||||
| Message
|
|
||||||
)[] = (
|
|
||||||
Object.entries(body).filter(
|
Object.entries(body).filter(
|
||||||
(e) => e[0] !== "currentPassword"
|
(e) => e[0] !== "poi"
|
||||||
) as [
|
)
|
||||||
keyof Accounts.Account,
|
|
||||||
UserUpdateParameters[keyof Accounts.Account],
|
|
||||||
][]
|
|
||||||
).map(([x, v]) => {
|
).map(([x, v]) => {
|
||||||
if (!validators[x])
|
let validator = validators[x as keyof typeof validators]!
|
||||||
return [
|
|
||||||
400,
|
|
||||||
`the ${x} parameter cannot be set or is not a valid parameter`,
|
|
||||||
] as Message
|
|
||||||
|
|
||||||
let validator = validators[x]!
|
if (target == actor && tokenType !== "User") {
|
||||||
|
if (validator.noAPIAccess)
|
||||||
let check = validator.schema.safeParse(v)
|
return [400, "no API access to this route"]
|
||||||
if (!check.success)
|
}
|
||||||
return [400, issuesToMessage(check.error.issues)]
|
|
||||||
|
|
||||||
return [
|
return [
|
||||||
x,
|
x,
|
||||||
|
@ -322,20 +327,24 @@ export default function (files: Files) {
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
router.delete("/:user", requiresAccount, noAPIAccess, async (ctx) => {
|
router.delete("/:user", noAPIAccess, async (ctx) => {
|
||||||
let acc = ctx.get("target")
|
let actor = ctx.get("account")
|
||||||
|
let target = ctx.get("target")
|
||||||
|
|
||||||
auth.AuthTokens.filter((e) => e.account == acc?.id).forEach((token) => {
|
if (actor == target && !verifyPoi(actor.id, ctx.req.query("poi")))
|
||||||
|
return ServeError(ctx, 403, "no proof of identity provided")
|
||||||
|
|
||||||
|
auth.AuthTokens.filter((e) => e.account == target?.id).forEach((token) => {
|
||||||
auth.invalidate(token.token)
|
auth.invalidate(token.token)
|
||||||
})
|
})
|
||||||
|
|
||||||
await Accounts.deleteAccount(acc.id)
|
await Accounts.deleteAccount(target.id)
|
||||||
|
|
||||||
if (acc.email) {
|
if (target.email) {
|
||||||
await sendMail(
|
await sendMail(
|
||||||
acc.email,
|
target.email,
|
||||||
"Notice of account deletion",
|
"Notice of account deletion",
|
||||||
`Your account, <span username>${acc.username}</span>, has been removed. Thank you for using monofile.`
|
`Your account, <span username>${target.username}</span>, has been removed. Thank you for using monofile.`
|
||||||
).catch()
|
).catch()
|
||||||
return ctx.text("OK")
|
return ctx.text("OK")
|
||||||
}
|
}
|
||||||
|
@ -343,7 +352,7 @@ export default function (files: Files) {
|
||||||
return ctx.text("account deleted")
|
return ctx.text("account deleted")
|
||||||
})
|
})
|
||||||
|
|
||||||
router.get("/:user", requiresAccount, async (ctx) => {
|
router.get("/:user", async (ctx) => {
|
||||||
let acc = ctx.get("target")
|
let acc = ctx.get("target")
|
||||||
let sessionToken = auth.tokenFor(ctx)!
|
let sessionToken = auth.tokenFor(ctx)!
|
||||||
|
|
||||||
|
|
80
src/server/routes/api/v1/account/prove.ts
Normal file
80
src/server/routes/api/v1/account/prove.ts
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
// Modules
|
||||||
|
|
||||||
|
import { type Context, Hono } from "hono"
|
||||||
|
import { getCookie, setCookie } from "hono/cookie"
|
||||||
|
|
||||||
|
// Libs
|
||||||
|
|
||||||
|
import Files from "../../../../lib/files.js"
|
||||||
|
import * as Accounts from "../../../../lib/accounts.js"
|
||||||
|
import * as auth from "../../../../lib/auth.js"
|
||||||
|
import {
|
||||||
|
assertAPI,
|
||||||
|
getAccount,
|
||||||
|
getTarget,
|
||||||
|
issuesToMessage,
|
||||||
|
login,
|
||||||
|
noAPIAccess,
|
||||||
|
requiresAccount,
|
||||||
|
requiresPermissions,
|
||||||
|
requiresTarget,
|
||||||
|
scheme,
|
||||||
|
} from "../../../../lib/middleware.js"
|
||||||
|
import ServeError from "../../../../lib/errors.js"
|
||||||
|
|
||||||
|
import Configuration from "../../../../lib/config.js"
|
||||||
|
import { AccountSchemas, AuthSchemas, FileSchemas } from "../../../../lib/schemas/index.js"
|
||||||
|
import { z } from "zod"
|
||||||
|
import { BlankInput } from "hono/types"
|
||||||
|
import * as CodeMgr from "../../../../lib/codes.js"
|
||||||
|
|
||||||
|
const router = new Hono<{
|
||||||
|
Variables: {
|
||||||
|
account?: Accounts.Account
|
||||||
|
target: Accounts.Account
|
||||||
|
parsedScheme: any
|
||||||
|
}
|
||||||
|
}>()
|
||||||
|
|
||||||
|
router.use(getAccount, getTarget, requiresTarget, noAPIAccess)
|
||||||
|
|
||||||
|
const ProofCreationSchema = z.object({
|
||||||
|
password: z.string().optional(),
|
||||||
|
/*auth: AuthSchemas.2fa.any*/ // if we add 2fa...
|
||||||
|
})
|
||||||
|
|
||||||
|
export default function () {
|
||||||
|
|
||||||
|
router.get("/", async (ctx) => {
|
||||||
|
return ctx.json(["none"]) // if we add 2fa in the future, return available 2fa methods
|
||||||
|
})
|
||||||
|
|
||||||
|
router.post("/", scheme(
|
||||||
|
ProofCreationSchema
|
||||||
|
), async (ctx) => {
|
||||||
|
|
||||||
|
let actor = ctx.get("account")
|
||||||
|
let target = ctx.get("target")
|
||||||
|
let body = ctx.get("parsedScheme") as z.infer<typeof ProofCreationSchema>
|
||||||
|
|
||||||
|
if (true /*(!actor || !actor.2fa)*/) {
|
||||||
|
// if there is no actor,
|
||||||
|
// or if the actor doesn't have 2fa
|
||||||
|
// check their password first
|
||||||
|
|
||||||
|
if (!Accounts.password.check(target.id, body.password||""))
|
||||||
|
return ServeError(ctx, 401, `bad password`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// if actor does have 2fa in an else block here
|
||||||
|
|
||||||
|
return ctx.text(new CodeMgr.Code(
|
||||||
|
"identityProof",
|
||||||
|
target.id,
|
||||||
|
Boolean(actor), // so that you can only log in with proofs created when logged out
|
||||||
|
5 * 60 * 1000
|
||||||
|
).id)
|
||||||
|
})
|
||||||
|
|
||||||
|
return router
|
||||||
|
}
|
|
@ -12,6 +12,10 @@ export default {
|
||||||
"file": "account/access",
|
"file": "account/access",
|
||||||
"to": "/account/:user/access"
|
"to": "/account/:user/access"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"file": "account/prove",
|
||||||
|
"to": "/account/:user/proveIdentity"
|
||||||
|
},
|
||||||
"session",
|
"session",
|
||||||
{
|
{
|
||||||
"file": "index",
|
"file": "index",
|
||||||
|
|
|
@ -17,7 +17,7 @@ import { FileSchemas } from "../../../../lib/schemas/index.js"
|
||||||
const router = new Hono<{
|
const router = new Hono<{
|
||||||
Variables: {
|
Variables: {
|
||||||
account: Accounts.Account,
|
account: Accounts.Account,
|
||||||
parsedSchema: any
|
parsedScheme: any
|
||||||
},
|
},
|
||||||
Bindings: HttpBindings
|
Bindings: HttpBindings
|
||||||
}>()
|
}>()
|
||||||
|
|
Loading…
Reference in a new issue