Add admin.updateAccountEamil ()

* -add admin capability to update account email

* pr feedback
This commit is contained in:
Daniel Holmgren 2023-04-13 14:43:53 -05:00 committed by GitHub
parent 4aa9103fb5
commit d8b50c73e4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 237 additions and 0 deletions
lexicons/com/atproto/admin
packages
api/src/client
pds
src
api/com/atproto/admin
lexicon
services/account
tests

@ -0,0 +1,25 @@
{
"lexicon": 1,
"id": "com.atproto.admin.updateAccountEmail",
"defs": {
"main": {
"type": "procedure",
"description": "Administrative action to update an account's email",
"input": {
"encoding": "application/json",
"schema": {
"type": "object",
"required": ["account", "email"],
"properties": {
"account": {
"type": "string",
"format": "at-identifier",
"description": "The handle or DID of the repo."
},
"email": {"type": "string"}
}
}
}
}
}
}

@ -20,6 +20,7 @@ import * as ComAtprotoAdminResolveModerationReports from './types/com/atproto/ad
import * as ComAtprotoAdminReverseModerationAction from './types/com/atproto/admin/reverseModerationAction'
import * as ComAtprotoAdminSearchRepos from './types/com/atproto/admin/searchRepos'
import * as ComAtprotoAdminTakeModerationAction from './types/com/atproto/admin/takeModerationAction'
import * as ComAtprotoAdminUpdateAccountEmail from './types/com/atproto/admin/updateAccountEmail'
import * as ComAtprotoAdminUpdateAccountHandle from './types/com/atproto/admin/updateAccountHandle'
import * as ComAtprotoIdentityResolveHandle from './types/com/atproto/identity/resolveHandle'
import * as ComAtprotoIdentityUpdateHandle from './types/com/atproto/identity/updateHandle'
@ -108,6 +109,7 @@ export * as ComAtprotoAdminResolveModerationReports from './types/com/atproto/ad
export * as ComAtprotoAdminReverseModerationAction from './types/com/atproto/admin/reverseModerationAction'
export * as ComAtprotoAdminSearchRepos from './types/com/atproto/admin/searchRepos'
export * as ComAtprotoAdminTakeModerationAction from './types/com/atproto/admin/takeModerationAction'
export * as ComAtprotoAdminUpdateAccountEmail from './types/com/atproto/admin/updateAccountEmail'
export * as ComAtprotoAdminUpdateAccountHandle from './types/com/atproto/admin/updateAccountHandle'
export * as ComAtprotoIdentityResolveHandle from './types/com/atproto/identity/resolveHandle'
export * as ComAtprotoIdentityUpdateHandle from './types/com/atproto/identity/updateHandle'
@ -398,6 +400,17 @@ export class AdminNS {
})
}
updateAccountEmail(
data?: ComAtprotoAdminUpdateAccountEmail.InputSchema,
opts?: ComAtprotoAdminUpdateAccountEmail.CallOptions,
): Promise<ComAtprotoAdminUpdateAccountEmail.Response> {
return this._service.xrpc
.call('com.atproto.admin.updateAccountEmail', opts?.qp, data, opts)
.catch((e) => {
throw ComAtprotoAdminUpdateAccountEmail.toKnownErr(e)
})
}
updateAccountHandle(
data?: ComAtprotoAdminUpdateAccountHandle.InputSchema,
opts?: ComAtprotoAdminUpdateAccountHandle.CallOptions,

@ -1063,6 +1063,33 @@ export const schemaDict = {
},
},
},
ComAtprotoAdminUpdateAccountEmail: {
lexicon: 1,
id: 'com.atproto.admin.updateAccountEmail',
defs: {
main: {
type: 'procedure',
description: "Administrative action to update an account's email",
input: {
encoding: 'application/json',
schema: {
type: 'object',
required: ['account', 'email'],
properties: {
account: {
type: 'string',
format: 'at-identifier',
description: 'The handle or DID of the repo.',
},
email: {
type: 'string',
},
},
},
},
},
},
},
ComAtprotoAdminUpdateAccountHandle: {
lexicon: 1,
id: 'com.atproto.admin.updateAccountHandle',
@ -4796,6 +4823,7 @@ export const ids = {
'com.atproto.admin.reverseModerationAction',
ComAtprotoAdminSearchRepos: 'com.atproto.admin.searchRepos',
ComAtprotoAdminTakeModerationAction: 'com.atproto.admin.takeModerationAction',
ComAtprotoAdminUpdateAccountEmail: 'com.atproto.admin.updateAccountEmail',
ComAtprotoAdminUpdateAccountHandle: 'com.atproto.admin.updateAccountHandle',
ComAtprotoIdentityResolveHandle: 'com.atproto.identity.resolveHandle',
ComAtprotoIdentityUpdateHandle: 'com.atproto.identity.updateHandle',

@ -0,0 +1,34 @@
/**
* GENERATED CODE - DO NOT MODIFY
*/
import { Headers, XRPCError } from '@atproto/xrpc'
import { ValidationResult, BlobRef } from '@atproto/lexicon'
import { isObj, hasProp } from '../../../../util'
import { lexicons } from '../../../../lexicons'
import { CID } from 'multiformats/cid'
export interface QueryParams {}
export interface InputSchema {
/** The handle or DID of the repo. */
account: string
email: string
[k: string]: unknown
}
export interface CallOptions {
headers?: Headers
qp?: QueryParams
encoding: 'application/json'
}
export interface Response {
success: boolean
headers: Headers
}
export function toKnownErr(e: any) {
if (e instanceof XRPCError) {
}
return e
}

@ -13,6 +13,7 @@ import getModerationReports from './getModerationReports'
import disableInviteCodes from './disableInviteCodes'
import getInviteCodes from './getInviteCodes'
import updateAccountHandle from './updateAccountHandle'
import updateAccountEmail from './updateAccountEmail'
export default function (server: Server, ctx: AppContext) {
resolveModerationReports(server, ctx)
@ -28,4 +29,5 @@ export default function (server: Server, ctx: AppContext) {
disableInviteCodes(server, ctx)
getInviteCodes(server, ctx)
updateAccountHandle(server, ctx)
updateAccountEmail(server, ctx)
}

@ -0,0 +1,21 @@
import { InvalidRequestError } from '@atproto/xrpc-server'
import { Server } from '../../../../lexicon'
import AppContext from '../../../../context'
export default function (server: Server, ctx: AppContext) {
server.com.atproto.admin.updateAccountEmail({
auth: ctx.adminVerifier,
handler: async ({ input }) => {
await ctx.db.transaction(async (dbTxn) => {
const accntService = ctx.services.account(dbTxn)
const account = await accntService.getAccount(input.body.account)
if (!account) {
throw new InvalidRequestError(
`Account does not exist: ${input.body.account}`,
)
}
await accntService.updateEmail(account.did, input.body.email)
})
},
})
}

@ -21,6 +21,7 @@ import * as ComAtprotoAdminResolveModerationReports from './types/com/atproto/ad
import * as ComAtprotoAdminReverseModerationAction from './types/com/atproto/admin/reverseModerationAction'
import * as ComAtprotoAdminSearchRepos from './types/com/atproto/admin/searchRepos'
import * as ComAtprotoAdminTakeModerationAction from './types/com/atproto/admin/takeModerationAction'
import * as ComAtprotoAdminUpdateAccountEmail from './types/com/atproto/admin/updateAccountEmail'
import * as ComAtprotoAdminUpdateAccountHandle from './types/com/atproto/admin/updateAccountHandle'
import * as ComAtprotoIdentityResolveHandle from './types/com/atproto/identity/resolveHandle'
import * as ComAtprotoIdentityUpdateHandle from './types/com/atproto/identity/updateHandle'
@ -257,6 +258,16 @@ export class AdminNS {
return this._server.xrpc.method(nsid, cfg)
}
updateAccountEmail<AV extends AuthVerifier>(
cfg: ConfigOf<
AV,
ComAtprotoAdminUpdateAccountEmail.Handler<ExtractAuth<AV>>
>,
) {
const nsid = 'com.atproto.admin.updateAccountEmail' // @ts-ignore
return this._server.xrpc.method(nsid, cfg)
}
updateAccountHandle<AV extends AuthVerifier>(
cfg: ConfigOf<
AV,

@ -1063,6 +1063,33 @@ export const schemaDict = {
},
},
},
ComAtprotoAdminUpdateAccountEmail: {
lexicon: 1,
id: 'com.atproto.admin.updateAccountEmail',
defs: {
main: {
type: 'procedure',
description: "Administrative action to update an account's email",
input: {
encoding: 'application/json',
schema: {
type: 'object',
required: ['account', 'email'],
properties: {
account: {
type: 'string',
format: 'at-identifier',
description: 'The handle or DID of the repo.',
},
email: {
type: 'string',
},
},
},
},
},
},
},
ComAtprotoAdminUpdateAccountHandle: {
lexicon: 1,
id: 'com.atproto.admin.updateAccountHandle',
@ -4796,6 +4823,7 @@ export const ids = {
'com.atproto.admin.reverseModerationAction',
ComAtprotoAdminSearchRepos: 'com.atproto.admin.searchRepos',
ComAtprotoAdminTakeModerationAction: 'com.atproto.admin.takeModerationAction',
ComAtprotoAdminUpdateAccountEmail: 'com.atproto.admin.updateAccountEmail',
ComAtprotoAdminUpdateAccountHandle: 'com.atproto.admin.updateAccountHandle',
ComAtprotoIdentityResolveHandle: 'com.atproto.identity.resolveHandle',
ComAtprotoIdentityUpdateHandle: 'com.atproto.identity.updateHandle',

@ -0,0 +1,37 @@
/**
* GENERATED CODE - DO NOT MODIFY
*/
import express from 'express'
import { ValidationResult, BlobRef } from '@atproto/lexicon'
import { lexicons } from '../../../../lexicons'
import { isObj, hasProp } from '../../../../util'
import { CID } from 'multiformats/cid'
import { HandlerAuth } from '@atproto/xrpc-server'
export interface QueryParams {}
export interface InputSchema {
/** The handle or DID of the repo. */
account: string
email: string
[k: string]: unknown
}
export interface HandlerInput {
encoding: 'application/json'
body: InputSchema
}
export interface HandlerError {
status: number
message?: string
}
export type HandlerOutput = HandlerError | void
export type Handler<HA extends HandlerAuth = never> = (ctx: {
auth: HA
params: QueryParams
input: HandlerInput
req: express.Request
res: express.Response
}) => Promise<HandlerOutput> | HandlerOutput

@ -145,6 +145,14 @@ export class AccountService {
await sequenceHandleUpdate(this.db, did, handle)
}
async updateEmail(did: string, email: string) {
await this.db.db
.updateTable('user_account')
.set({ email: email.toLowerCase() })
.where('did', '=', did)
.executeTakeFirst()
}
async updateUserPassword(did: string, password: string) {
const passwordScrypt = await scrypt.hash(password)
await this.db.db

@ -168,6 +168,36 @@ describe('account', () => {
])
})
it('allows administrative email updates', async () => {
await agent.api.com.atproto.admin.updateAccountEmail(
{
account: handle,
email: 'alIce-NEw@teST.com',
},
{
encoding: 'application/json',
headers: { authorization: util.adminAuth() },
},
)
const accnt = await ctx.services.account(ctx.db).getAccount(handle)
expect(accnt?.email).toBe('alice-new@test.com')
await agent.api.com.atproto.admin.updateAccountEmail(
{
account: did,
email,
},
{
encoding: 'application/json',
headers: { authorization: util.adminAuth() },
},
)
const accnt2 = await ctx.services.account(ctx.db).getAccount(handle)
expect(accnt2?.email).toBe(email)
})
it('disallows duplicate email addresses and handles', async () => {
const inviteCode = await createInviteCode(agent, 2)
const email = 'bob@test.com'