diff options
| author | syuilo <Syuilotan@yahoo.co.jp> | 2021-10-16 19:55:44 +0900 |
|---|---|---|
| committer | syuilo <Syuilotan@yahoo.co.jp> | 2021-10-16 19:55:44 +0900 |
| commit | 8a1f3a4c0b5732d0f08f0788d93c5934de8960c8 (patch) | |
| tree | be6fbcf3a1bbd78306d91e19ef6f3e7023f41561 /src/server/api | |
| parent | Merge branch 'develop' (diff) | |
| parent | 12.92.0 (diff) | |
| download | misskey-8a1f3a4c0b5732d0f08f0788d93c5934de8960c8.tar.gz misskey-8a1f3a4c0b5732d0f08f0788d93c5934de8960c8.tar.bz2 misskey-8a1f3a4c0b5732d0f08f0788d93c5934de8960c8.zip | |
Merge branch 'develop'
Diffstat (limited to 'src/server/api')
| -rw-r--r-- | src/server/api/common/signup.ts | 26 | ||||
| -rw-r--r-- | src/server/api/endpoints/admin/accounts/create.ts | 5 | ||||
| -rw-r--r-- | src/server/api/endpoints/admin/drive/files.ts | 2 | ||||
| -rw-r--r-- | src/server/api/endpoints/admin/queue/inbox-delayed.ts | 2 | ||||
| -rw-r--r-- | src/server/api/endpoints/admin/update-meta.ts | 8 | ||||
| -rw-r--r-- | src/server/api/endpoints/ap/get.ts | 8 | ||||
| -rw-r--r-- | src/server/api/endpoints/ap/show.ts | 8 | ||||
| -rw-r--r-- | src/server/api/endpoints/blocking/create.ts | 14 | ||||
| -rw-r--r-- | src/server/api/endpoints/drive/files/update.ts | 3 | ||||
| -rw-r--r-- | src/server/api/endpoints/drive/files/upload-from-url.ts | 3 | ||||
| -rw-r--r-- | src/server/api/endpoints/email-address/available.ts | 37 | ||||
| -rw-r--r-- | src/server/api/endpoints/i/notifications.ts | 11 | ||||
| -rw-r--r-- | src/server/api/endpoints/i/update.ts | 2 | ||||
| -rw-r--r-- | src/server/api/endpoints/meta.ts | 6 | ||||
| -rw-r--r-- | src/server/api/endpoints/users/groups/leave.ts | 50 | ||||
| -rw-r--r-- | src/server/api/index.ts | 2 | ||||
| -rw-r--r-- | src/server/api/private/signup-pending.ts | 35 | ||||
| -rw-r--r-- | src/server/api/private/signup.ts | 62 |
18 files changed, 254 insertions, 30 deletions
diff --git a/src/server/api/common/signup.ts b/src/server/api/common/signup.ts index eb3aa09c8c..2ba0d8e479 100644 --- a/src/server/api/common/signup.ts +++ b/src/server/api/common/signup.ts @@ -11,20 +11,30 @@ import { UserKeypair } from '@/models/entities/user-keypair'; import { usersChart } from '@/services/chart/index'; import { UsedUsername } from '@/models/entities/used-username'; -export async function signup(username: User['username'], password: UserProfile['password'], host: string | null = null) { +export async function signup(opts: { + username: User['username']; + password?: string | null; + passwordHash?: UserProfile['password'] | null; + host?: string | null; +}) { + const { username, password, passwordHash, host } = opts; + let hash = passwordHash; + // Validate username if (!Users.validateLocalUsername.ok(username)) { throw new Error('INVALID_USERNAME'); } - // Validate password - if (!Users.validatePassword.ok(password)) { - throw new Error('INVALID_PASSWORD'); - } + if (password != null && passwordHash == null) { + // Validate password + if (!Users.validatePassword.ok(password)) { + throw new Error('INVALID_PASSWORD'); + } - // Generate hash of password - const salt = await bcrypt.genSalt(8); - const hash = await bcrypt.hash(password, salt); + // Generate hash of password + const salt = await bcrypt.genSalt(8); + hash = await bcrypt.hash(password, salt); + } // Generate secret const secret = generateUserToken(); diff --git a/src/server/api/endpoints/admin/accounts/create.ts b/src/server/api/endpoints/admin/accounts/create.ts index 9691b9c7e3..fa15e84f77 100644 --- a/src/server/api/endpoints/admin/accounts/create.ts +++ b/src/server/api/endpoints/admin/accounts/create.ts @@ -35,7 +35,10 @@ export default define(meta, async (ps, _me) => { })) === 0; if (!noUsers && !me?.isAdmin) throw new Error('access denied'); - const { account, secret } = await signup(ps.username, ps.password); + const { account, secret } = await signup({ + username: ps.username, + password: ps.password, + }); const res = await Users.pack(account, account, { detail: true, diff --git a/src/server/api/endpoints/admin/drive/files.ts b/src/server/api/endpoints/admin/drive/files.ts index c0788c8e02..fe1c799805 100644 --- a/src/server/api/endpoints/admin/drive/files.ts +++ b/src/server/api/endpoints/admin/drive/files.ts @@ -25,7 +25,7 @@ export const meta = { }, type: { - validator: $.optional.nullable.str.match(/^[a-zA-Z\/\-*]+$/) + validator: $.optional.nullable.str.match(/^[a-zA-Z0-9\/\-*]+$/) }, origin: { diff --git a/src/server/api/endpoints/admin/queue/inbox-delayed.ts b/src/server/api/endpoints/admin/queue/inbox-delayed.ts index 59e5c834ed..1925906c28 100644 --- a/src/server/api/endpoints/admin/queue/inbox-delayed.ts +++ b/src/server/api/endpoints/admin/queue/inbox-delayed.ts @@ -1,6 +1,6 @@ import { URL } from 'url'; import define from '../../../define'; -import { inboxQueue } from '@/queue/index'; +import { inboxQueue } from '@/queue/queues'; export const meta = { tags: ['admin'], diff --git a/src/server/api/endpoints/admin/update-meta.ts b/src/server/api/endpoints/admin/update-meta.ts index 46f30fef7d..55447098dc 100644 --- a/src/server/api/endpoints/admin/update-meta.ts +++ b/src/server/api/endpoints/admin/update-meta.ts @@ -93,6 +93,10 @@ export const meta = { validator: $.optional.bool, }, + emailRequiredForSignup: { + validator: $.optional.bool, + }, + enableHcaptcha: { validator: $.optional.bool, }, @@ -374,6 +378,10 @@ export default define(meta, async (ps, me) => { set.proxyRemoteFiles = ps.proxyRemoteFiles; } + if (ps.emailRequiredForSignup !== undefined) { + set.emailRequiredForSignup = ps.emailRequiredForSignup; + } + if (ps.enableHcaptcha !== undefined) { set.enableHcaptcha = ps.enableHcaptcha; } diff --git a/src/server/api/endpoints/ap/get.ts b/src/server/api/endpoints/ap/get.ts index 2cffce1f16..78919f43b0 100644 --- a/src/server/api/endpoints/ap/get.ts +++ b/src/server/api/endpoints/ap/get.ts @@ -2,11 +2,17 @@ import $ from 'cafy'; import define from '../../define'; import Resolver from '@/remote/activitypub/resolver'; import { ApiError } from '../../error'; +import * as ms from 'ms'; export const meta = { tags: ['federation'], - requireCredential: false as const, + requireCredential: true as const, + + limit: { + duration: ms('1hour'), + max: 30 + }, params: { uri: { diff --git a/src/server/api/endpoints/ap/show.ts b/src/server/api/endpoints/ap/show.ts index aa0dae070c..2280d93724 100644 --- a/src/server/api/endpoints/ap/show.ts +++ b/src/server/api/endpoints/ap/show.ts @@ -11,11 +11,17 @@ import { Note } from '@/models/entities/note'; import { User } from '@/models/entities/user'; import { fetchMeta } from '@/misc/fetch-meta'; import { isActor, isPost, getApId } from '@/remote/activitypub/type'; +import * as ms from 'ms'; export const meta = { tags: ['federation'], - requireCredential: false as const, + requireCredential: true as const, + + limit: { + duration: ms('1hour'), + max: 30 + }, params: { uri: { diff --git a/src/server/api/endpoints/blocking/create.ts b/src/server/api/endpoints/blocking/create.ts index 1bf5cf374b..4deaa39974 100644 --- a/src/server/api/endpoints/blocking/create.ts +++ b/src/server/api/endpoints/blocking/create.ts @@ -43,6 +43,12 @@ export const meta = { code: 'ALREADY_BLOCKING', id: '787fed64-acb9-464a-82eb-afbd745b9614' }, + + cannotBlockModerator: { + message: 'Cannot block a moderator or an admin.', + code: 'CANNOT_BLOCK_MODERATOR', + id: '8544aaef-89fb-e470-9f6c-385d38b474f5' + } }, res: { @@ -76,8 +82,12 @@ export default define(meta, async (ps, user) => { throw new ApiError(meta.errors.alreadyBlocking); } - // Create blocking - await create(blocker, blockee); + try { + await create(blocker, blockee); + } catch (e) { + if (e.id === 'e42b7890-5e4d-9d9c-d54b-cf4dd30adfb5') throw new ApiError(meta.errors.cannotBlockModerator); + throw e; + } NoteWatchings.delete({ userId: blocker.id, diff --git a/src/server/api/endpoints/drive/files/update.ts b/src/server/api/endpoints/drive/files/update.ts index 1ef445625c..f277a9c3dc 100644 --- a/src/server/api/endpoints/drive/files/update.ts +++ b/src/server/api/endpoints/drive/files/update.ts @@ -4,6 +4,7 @@ import { publishDriveStream } from '@/services/stream'; import define from '../../../define'; import { ApiError } from '../../../error'; import { DriveFiles, DriveFolders } from '@/models/index'; +import { DB_MAX_IMAGE_COMMENT_LENGTH } from '@/misc/hard-limits'; export const meta = { tags: ['drive'], @@ -33,7 +34,7 @@ export const meta = { }, comment: { - validator: $.optional.nullable.str, + validator: $.optional.nullable.str.max(DB_MAX_IMAGE_COMMENT_LENGTH), default: undefined as any, } }, diff --git a/src/server/api/endpoints/drive/files/upload-from-url.ts b/src/server/api/endpoints/drive/files/upload-from-url.ts index f37f316efb..9f10a42d24 100644 --- a/src/server/api/endpoints/drive/files/upload-from-url.ts +++ b/src/server/api/endpoints/drive/files/upload-from-url.ts @@ -5,6 +5,7 @@ import uploadFromUrl from '@/services/drive/upload-from-url'; import define from '../../../define'; import { DriveFiles } from '@/models/index'; import { publishMainStream } from '@/services/stream'; +import { DB_MAX_IMAGE_COMMENT_LENGTH } from '@/misc/hard-limits'; export const meta = { tags: ['drive'], @@ -35,7 +36,7 @@ export const meta = { }, comment: { - validator: $.optional.nullable.str, + validator: $.optional.nullable.str.max(DB_MAX_IMAGE_COMMENT_LENGTH), default: null, }, diff --git a/src/server/api/endpoints/email-address/available.ts b/src/server/api/endpoints/email-address/available.ts new file mode 100644 index 0000000000..65fe6f9178 --- /dev/null +++ b/src/server/api/endpoints/email-address/available.ts @@ -0,0 +1,37 @@ +import $ from 'cafy'; +import define from '../../define'; +import { UserProfiles } from '@/models/index'; + +export const meta = { + tags: ['users'], + + requireCredential: false as const, + + params: { + emailAddress: { + validator: $.str + } + }, + + res: { + type: 'object' as const, + optional: false as const, nullable: false as const, + properties: { + available: { + type: 'boolean' as const, + optional: false as const, nullable: false as const, + } + } + } +}; + +export default define(meta, async (ps) => { + const exist = await UserProfiles.count({ + emailVerified: true, + email: ps.emailAddress, + }); + + return { + available: exist === 0 + }; +}); diff --git a/src/server/api/endpoints/i/notifications.ts b/src/server/api/endpoints/i/notifications.ts index 3c265a10c1..fcabbbc3dd 100644 --- a/src/server/api/endpoints/i/notifications.ts +++ b/src/server/api/endpoints/i/notifications.ts @@ -4,7 +4,7 @@ import { readNotification } from '../../common/read-notification'; import define from '../../define'; import { makePaginationQuery } from '../../common/make-pagination-query'; import { Notifications, Followings, Mutings, Users } from '@/models/index'; -import { notificationTypes } from '../../../../types'; +import { notificationTypes } from '@/types'; import read from '@/services/note/read'; export const meta = { @@ -33,6 +33,11 @@ export const meta = { default: false }, + unreadOnly: { + validator: $.optional.bool, + default: false + }, + markAsRead: { validator: $.optional.bool, default: true @@ -105,6 +110,10 @@ export default define(meta, async (ps, user) => { query.andWhere(`notification.type NOT IN (:...excludeTypes)`, { excludeTypes: ps.excludeTypes }); } + if (ps.unreadOnly) { + query.andWhere(`notification.isRead = false`); + } + const notifications = await query.take(ps.limit!).getMany(); // Mark all as read diff --git a/src/server/api/endpoints/i/update.ts b/src/server/api/endpoints/i/update.ts index fb7e12760e..9dd637251d 100644 --- a/src/server/api/endpoints/i/update.ts +++ b/src/server/api/endpoints/i/update.ts @@ -13,7 +13,7 @@ import { ApiError } from '../../error'; import { Users, DriveFiles, UserProfiles, Pages } from '@/models/index'; import { User } from '@/models/entities/user'; import { UserProfile } from '@/models/entities/user-profile'; -import { notificationTypes } from '../../../../types'; +import { notificationTypes } from '@/types'; import { normalizeForSearch } from '@/misc/normalize-for-search'; export const meta = { diff --git a/src/server/api/endpoints/meta.ts b/src/server/api/endpoints/meta.ts index 3f422dff07..ce21556243 100644 --- a/src/server/api/endpoints/meta.ts +++ b/src/server/api/endpoints/meta.ts @@ -104,6 +104,10 @@ export const meta = { type: 'boolean' as const, optional: false as const, nullable: false as const }, + emailRequiredForSignup: { + type: 'boolean' as const, + optional: false as const, nullable: false as const + }, enableHcaptcha: { type: 'boolean' as const, optional: false as const, nullable: false as const @@ -488,6 +492,7 @@ export default define(meta, async (ps, me) => { disableGlobalTimeline: instance.disableGlobalTimeline, driveCapacityPerLocalUserMb: instance.localDriveCapacityMb, driveCapacityPerRemoteUserMb: instance.remoteDriveCapacityMb, + emailRequiredForSignup: instance.emailRequiredForSignup, enableHcaptcha: instance.enableHcaptcha, hcaptchaSiteKey: instance.hcaptchaSiteKey, enableRecaptcha: instance.enableRecaptcha, @@ -537,6 +542,7 @@ export default define(meta, async (ps, me) => { registration: !instance.disableRegistration, localTimeLine: !instance.disableLocalTimeline, globalTimeLine: !instance.disableGlobalTimeline, + emailRequiredForSignup: instance.emailRequiredForSignup, elasticsearch: config.elasticsearch ? true : false, hcaptcha: instance.enableHcaptcha, recaptcha: instance.enableRecaptcha, diff --git a/src/server/api/endpoints/users/groups/leave.ts b/src/server/api/endpoints/users/groups/leave.ts new file mode 100644 index 0000000000..0e52f2abdf --- /dev/null +++ b/src/server/api/endpoints/users/groups/leave.ts @@ -0,0 +1,50 @@ +import $ from 'cafy'; +import { ID } from '@/misc/cafy-id'; +import define from '../../../define'; +import { ApiError } from '../../../error'; +import { UserGroups, UserGroupJoinings } from '@/models/index'; + +export const meta = { + tags: ['groups', 'users'], + + requireCredential: true as const, + + kind: 'write:user-groups', + + params: { + groupId: { + validator: $.type(ID), + }, + }, + + errors: { + noSuchGroup: { + message: 'No such group.', + code: 'NO_SUCH_GROUP', + id: '62780270-1f67-5dc0-daca-3eb510612e31' + }, + + youAreOwner: { + message: 'Your are the owner.', + code: 'YOU_ARE_OWNER', + id: 'b6d6e0c2-ef8a-9bb8-653d-79f4a3107c69' + }, + } +}; + +export default define(meta, async (ps, me) => { + // Fetch the group + const userGroup = await UserGroups.findOne({ + id: ps.groupId, + }); + + if (userGroup == null) { + throw new ApiError(meta.errors.noSuchGroup); + } + + if (me.id === userGroup.userId) { + throw new ApiError(meta.errors.youAreOwner); + } + + await UserGroupJoinings.delete({ userGroupId: userGroup.id, userId: me.id }); +}); diff --git a/src/server/api/index.ts b/src/server/api/index.ts index db35fdf9e0..82579075eb 100644 --- a/src/server/api/index.ts +++ b/src/server/api/index.ts @@ -12,6 +12,7 @@ import endpoints from './endpoints'; import handler from './api-handler'; import signup from './private/signup'; import signin from './private/signin'; +import signupPending from './private/signup-pending'; import discord from './service/discord'; import github from './service/github'; import twitter from './service/twitter'; @@ -65,6 +66,7 @@ for (const endpoint of endpoints) { router.post('/signup', signup); router.post('/signin', signin); +router.post('/signup-pending', signupPending); router.use(discord.routes()); router.use(github.routes()); diff --git a/src/server/api/private/signup-pending.ts b/src/server/api/private/signup-pending.ts new file mode 100644 index 0000000000..c0638a1cda --- /dev/null +++ b/src/server/api/private/signup-pending.ts @@ -0,0 +1,35 @@ +import * as Koa from 'koa'; +import { Users, UserPendings, UserProfiles } from '@/models/index'; +import { signup } from '../common/signup'; +import signin from '../common/signin'; + +export default async (ctx: Koa.Context) => { + const body = ctx.request.body; + + const code = body['code']; + + try { + const pendingUser = await UserPendings.findOneOrFail({ code }); + + const { account, secret } = await signup({ + username: pendingUser.username, + passwordHash: pendingUser.password, + }); + + UserPendings.delete({ + id: pendingUser.id, + }); + + const profile = await UserProfiles.findOneOrFail(account.id); + + await UserProfiles.update({ userId: profile.userId }, { + email: pendingUser.email, + emailVerified: true, + emailVerifyCode: null, + }); + + signin(ctx, account); + } catch (e) { + ctx.throw(400, e); + } +}; diff --git a/src/server/api/private/signup.ts b/src/server/api/private/signup.ts index ef61767f65..93caaea935 100644 --- a/src/server/api/private/signup.ts +++ b/src/server/api/private/signup.ts @@ -1,8 +1,13 @@ import * as Koa from 'koa'; +import rndstr from 'rndstr'; +import * as bcrypt from 'bcryptjs'; import { fetchMeta } from '@/misc/fetch-meta'; import { verifyHcaptcha, verifyRecaptcha } from '@/misc/captcha'; -import { Users, RegistrationTickets } from '@/models/index'; +import { Users, RegistrationTickets, UserPendings } from '@/models/index'; import { signup } from '../common/signup'; +import config from '@/config'; +import { sendEmail } from '@/services/send-email'; +import { genId } from '@/misc/gen-id'; export default async (ctx: Koa.Context) => { const body = ctx.request.body; @@ -29,8 +34,16 @@ export default async (ctx: Koa.Context) => { const password = body['password']; const host: string | null = process.env.NODE_ENV === 'test' ? (body['host'] || null) : null; const invitationCode = body['invitationCode']; + const emailAddress = body['emailAddress']; - if (instance && instance.disableRegistration) { + if (instance.emailRequiredForSignup) { + if (emailAddress == null || typeof emailAddress != 'string') { + ctx.status = 400; + return; + } + } + + if (instance.disableRegistration) { if (invitationCode == null || typeof invitationCode != 'string') { ctx.status = 400; return; @@ -48,18 +61,45 @@ export default async (ctx: Koa.Context) => { RegistrationTickets.delete(ticket.id); } - try { - const { account, secret } = await signup(username, password, host); + if (instance.emailRequiredForSignup) { + const code = rndstr('a-z0-9', 16); + + // Generate hash of password + const salt = await bcrypt.genSalt(8); + const hash = await bcrypt.hash(password, salt); - const res = await Users.pack(account, account, { - detail: true, - includeSecrets: true + await UserPendings.insert({ + id: genId(), + createdAt: new Date(), + code, + email: emailAddress, + username: username, + password: hash, }); - (res as any).token = secret; + const link = `${config.url}/signup-complete/${code}`; + + sendEmail(emailAddress, 'Signup', + `To complete signup, please click this link:<br><a href="${link}">${link}</a>`, + `To complete signup, please click this link: ${link}`); - ctx.body = res; - } catch (e) { - ctx.throw(400, e); + ctx.status = 204; + } else { + try { + const { account, secret } = await signup({ + username, password, host + }); + + const res = await Users.pack(account, account, { + detail: true, + includeSecrets: true + }); + + (res as any).token = secret; + + ctx.body = res; + } catch (e) { + ctx.throw(400, e); + } } }; |