diff --git a/package.json b/package.json index ca83dcae9..c1c749ce6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "calckey", - "version": "13.0.0-b7", + "version": "13.0.0-rc1", "codename": "aqua", "repository": { "type": "git", @@ -19,6 +19,7 @@ "start:test": "yarn workspace backend run start:test", "init": "yarn migrate", "migrate": "yarn workspace backend run migrate", + "revertMig": "yarn workspace backend run revertMig", "migrateandstart": "yarn migrate && yarn start", "gulp": "gulp build", "watch": "yarn dev", diff --git a/packages/backend/package.json b/packages/backend/package.json index 2f7a11984..2348e963e 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -7,6 +7,7 @@ "start": "node ./built/index.js", "start:test": "NODE_ENV=test node ./built/index.js", "migrate": "typeorm migration:run -d ormconfig.js", + "revertMig": "typeorm migration:revert -d ormconfig.js", "build": "tsc -p tsconfig.json || echo done. && tsc-alias -p tsconfig.json", "watch": "node watch.mjs", "lint": "eslint --quiet \"src/**/*.ts\"", diff --git a/packages/backend/src/remote/activitypub/renderer/index.ts b/packages/backend/src/remote/activitypub/renderer/index.ts index e6f976f5f..2e1fbf1dd 100644 --- a/packages/backend/src/remote/activitypub/renderer/index.ts +++ b/packages/backend/src/remote/activitypub/renderer/index.ts @@ -1,9 +1,9 @@ -import config from '@/config/index.js'; import { v4 as uuid } from 'uuid'; -import { IActivity } from '../type.js'; -import { LdSignature } from '../misc/ld-signature.js'; +import config from '@/config/index.js'; import { getUserKeypair } from '@/misc/keypair-store.js'; -import { User } from '@/models/entities/user.js'; +import type { User } from '@/models/entities/user.js'; +import { LdSignature } from '../misc/ld-signature.js'; +import type { IActivity } from '../type.js'; export const renderActivity = (x: any): IActivity | null => { if (x == null) return null; diff --git a/packages/backend/src/server/api/endpoints.ts b/packages/backend/src/server/api/endpoints.ts index d56dd316e..912998376 100644 --- a/packages/backend/src/server/api/endpoints.ts +++ b/packages/backend/src/server/api/endpoints.ts @@ -325,6 +325,10 @@ import * as ep___users_stats from './endpoints/users/stats.js'; import * as ep___fetchRss from './endpoints/fetch-rss.js'; import * as ep___admin_driveCapOverride from './endpoints/admin/drive-capacity-override.js'; +//Calckey Move +import * as ep___i_move from './endpoints/i/move.js'; +import * as ep___i_known_as from './endpoints/i/known-as.js'; + const eps = [ ['admin/meta', ep___admin_meta], ['admin/abuse-user-reports', ep___admin_abuseUserReports], @@ -489,6 +493,8 @@ const eps = [ ['hashtags/trend', ep___hashtags_trend], ['hashtags/users', ep___hashtags_users], ['i', ep___i], + ['i/known-as', ep___i_known_as], + ['i/move', ep___i_move], ['i/2fa/done', ep___i_2fa_done], ['i/2fa/key-done', ep___i_2fa_keyDone], ['i/2fa/password-less', ep___i_2fa_passwordLess], diff --git a/packages/backend/src/server/api/endpoints/i/known-as.ts b/packages/backend/src/server/api/endpoints/i/known-as.ts new file mode 100644 index 000000000..924d74de8 --- /dev/null +++ b/packages/backend/src/server/api/endpoints/i/known-as.ts @@ -0,0 +1,85 @@ +import { In } from 'typeorm'; +import { User } from '@/models/entities/user.js'; +import { Users, DriveFiles, Notes, Channels, Blockings } from '@/models/index.js'; +import { resolveUser } from '@/remote/resolve-user.js'; +import { ApiError } from '../../error.js'; +import acceptAllFollowRequests from '@/services/following/requests/accept-all.js'; +import { publishToFollowers } from '@/services/i/update.js'; +import { apiLogger } from '../../logger.js'; +import { publishMainStream, publishUserEvent } from '@/services/stream.js'; +import define from '../../define.js'; +import { DAY } from '@/const.js'; + +export const meta = { + tags: ['users'], + + secure: true, + requireCredential: true, + + limit: { + duration: DAY, + max: 30, + }, + + errors: { + noSuchUser: { + message: 'No such user.', + code: 'NO_SUCH_USER', + id: 'fcd2eef9-a9b2-4c4f-8624-038099e90aa5', + }, + notRemote: { + message: 'User not remote.', + code: 'NOT_REMOTE', + id: '4362f8dc-731f-4ad8-a694-be2a88922a24', + }, + } +} as const; + +export const paramDef = { + type: 'object', + properties: { + alsoKnownAs: { type: 'string' }, + }, + required: ['alsoKnownAs'], +} as const; + +// eslint-disable-next-line import/no-default-export +export default define(meta, paramDef, async (ps, user) => { + + if(!ps.alsoKnownAs) throw new ApiError(meta.errors.noSuchUser); + + let unfiltered: string = ps.alsoKnownAs; + + if(unfiltered.startsWith('@')) unfiltered = unfiltered.substring(1); + if(!unfiltered.includes('@')) throw new ApiError(meta.errors.notRemote); + + let userAddress: string[] = unfiltered.split("@"); + + const knownAs: User = await resolveUser(userAddress[0], userAddress[1]).catch(e => { + apiLogger.warn(`failed to resolve remote user: ${e}`); + throw new ApiError(meta.errors.noSuchUser); + }); + + const updates = {} as Partial; + + if(!knownAs.uri) knownAs.uri = ""; + updates.alsoKnownAs = [knownAs.uri]; + + await Users.update(user.id, updates); + + const iObj = await Users.pack(user.id, user, { + detail: true, + includeSecrets: true, + }); + + // Publish meUpdated event + publishMainStream(user.id, 'meUpdated', iObj); + + if (user.isLocked === false) { + acceptAllFollowRequests(user); + } + + publishToFollowers(user.id); + + return iObj; +}); diff --git a/packages/backend/src/server/api/endpoints/i/move.ts b/packages/backend/src/server/api/endpoints/i/move.ts index c67a7bdf5..e6bd03601 100644 --- a/packages/backend/src/server/api/endpoints/i/move.ts +++ b/packages/backend/src/server/api/endpoints/i/move.ts @@ -1,12 +1,15 @@ -import { In } from 'typeorm'; -import { User } from '@/models/entities/user.js'; -import { Users, DriveFiles, Notes, Channels, Blockings } from '@/models/index.js'; -import { ApiError } from '../../error.js'; -import define from '../../define.js'; +import type { User } from '@/models/entities/user.js'; +import { resolveUser } from '@/remote/resolve-user.js'; import { DAY } from '@/const.js'; +import DeliverManager from '@/remote/activitypub/deliver-manager.js'; +import { renderActivity } from '@/remote/activitypub/renderer/index.js'; +import type { IActivity } from '@/remote/activitypub/type.js'; +import define from '../../define.js'; +import { ApiError } from '../../error.js'; +import { apiLogger } from '../../logger.js'; export const meta = { - tags: ['notes'], + tags: ['users'], secure: true, requireCredential: true, @@ -27,19 +30,62 @@ export const meta = { code: 'REMOTE_ACCOUNT_FORBIDS', id: 'b5c90186-4ab0-49c8-9bba-a1f766282ba4', }, + notRemote: { + message: 'User not remote.', + code: 'NOT_REMOTE', + id: '4362f8dc-731f-4ad8-a694-be2a88922a24', + }, }, } as const; export const paramDef = { type: 'object', properties: { - alsoKnownAs: { type: 'string' }, + moveToAccount: { type: 'string' }, }, - required: ['alsoKnownAs'], + required: ['moveToAccount'], } as const; // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - // TODO - return; + if (!ps.moveToAccount) throw new ApiError(meta.errors.noSuchMoveTarget); + + let unfiltered: string = ps.moveToAccount; + + if (unfiltered.startsWith('@')) unfiltered = unfiltered.substring(1); + if (!unfiltered.includes('@')) throw new ApiError(meta.errors.notRemote); + const userAddress: string[] = unfiltered.split('@'); + + const moveTo: User = await resolveUser(userAddress[0], userAddress[1]).catch(e => { + apiLogger.warn(`failed to resolve remote user: ${e}`); + throw new ApiError(meta.errors.noSuchMoveTarget); + }); + + let allowed = false; + + moveTo.alsoKnownAs?.forEach(element => { + if (user.uri?.includes(element)) allowed = true; + }); + + if (!allowed || !moveTo.uri || !user.uri) throw new ApiError(meta.errors.remoteAccountForbids); + + (async (): Promise => { + const moveAct = await moveActivity(moveTo.uri!, user.uri!); + const dm = new DeliverManager(user, moveAct); + dm.addFollowersRecipe(); + dm.execute(); + })(); + return true; }); + +async function moveActivity(to: string, from: string): Promise { + const activity = { + id: 'foo', + actor: from, + type: 'Move', + object: from, + target: to, + } as any; + + return renderActivity(activity); +} diff --git a/packages/client/src/pages/settings/migration.vue b/packages/client/src/pages/settings/migration.vue index 5fca8b837..d71a8aaf9 100644 --- a/packages/client/src/pages/settings/migration.vue +++ b/packages/client/src/pages/settings/migration.vue @@ -2,7 +2,7 @@
- + @@ -30,19 +30,19 @@ import * as os from '@/os'; import { i18n } from '@/i18n'; import { definePageMetadata } from '@/scripts/page-metadata'; -let alsoKnownAs = $ref(''); +let moveToAccount = $ref(''); let accountAlias = $ref(''); async function save(): Promise { - // os.apiWithDialog('i/move', { - // alsoKnownAs: alsoKnownAs, - // }); + os.apiWithDialog('i/known-as', { + alsoKnownAs: accountAlias, + }); } async function move(): Promise { // TODO: PROMPT FOR CONFIRMATION os.api('i/move', { - alsoKnownAs: alsoKnownAs, + moveToAccount: moveToAccount, }); }