Merge branch 'notification-read-api' into swn

This commit is contained in:
tamaina 2021-11-14 16:39:20 +09:00
commit ea9f0be129
26 changed files with 688 additions and 618 deletions

View file

@ -10,6 +10,17 @@
--> -->
## 12.x.x (unreleased)
### Improvements
- アカウント削除に確認ダイアログを出すように
### Bugfixes
## 12.96.1 (2021/11/13)
### Improvements
- npm scriptの互換性を向上
## 12.96.0 (2021/11/13) ## 12.96.0 (2021/11/13)
### Improvements ### Improvements

View file

@ -29,7 +29,6 @@ COPY --from=builder /misskey/built ./built
COPY --from=builder /misskey/packages/backend/node_modules ./packages/backend/node_modules COPY --from=builder /misskey/packages/backend/node_modules ./packages/backend/node_modules
COPY --from=builder /misskey/packages/backend/built ./packages/backend/built COPY --from=builder /misskey/packages/backend/built ./packages/backend/built
COPY --from=builder /misskey/packages/client/node_modules ./packages/client/node_modules COPY --from=builder /misskey/packages/client/node_modules ./packages/client/node_modules
COPY --from=builder /misskey/packages/client/built ./packages/client/built
COPY . ./ COPY . ./
CMD ["npm", "run", "migrateandstart"] CMD ["npm", "run", "migrateandstart"]

View file

@ -806,6 +806,7 @@ muteThread: "スレッドをミュート"
unmuteThread: "スレッドのミュートを解除" unmuteThread: "スレッドのミュートを解除"
ffVisibility: "つながりの公開範囲" ffVisibility: "つながりの公開範囲"
ffVisibilityDescription: "自分のフォロー/フォロワー情報の公開範囲を設定できます。" ffVisibilityDescription: "自分のフォロー/フォロワー情報の公開範囲を設定できます。"
deleteAccountConfirm: "アカウントが削除されます。よろしいですか?"
_emailUnavailable: _emailUnavailable:
used: "既に使用されています" used: "既に使用されています"

View file

@ -1,6 +1,6 @@
{ {
"name": "misskey", "name": "misskey",
"version": "12.96.0", "version": "12.96.1",
"codename": "indigo", "codename": "indigo",
"repository": { "repository": {
"type": "git", "type": "git",
@ -29,7 +29,8 @@
"test": "npm run mocha", "test": "npm run mocha",
"format": "gulp format", "format": "gulp format",
"clean": "node ./scripts/clean.js", "clean": "node ./scripts/clean.js",
"clean-all": "node ./scripts/clean-all.js" "clean-all": "node ./scripts/clean-all.js",
"cleanall": "npm run clean-all"
}, },
"dependencies": { "dependencies": {
"@types/gulp": "4.0.9", "@types/gulp": "4.0.9",
@ -39,7 +40,8 @@
"gulp-cssnano": "2.1.3", "gulp-cssnano": "2.1.3",
"gulp-rename": "2.0.0", "gulp-rename": "2.0.0",
"gulp-replace": "1.1.3", "gulp-replace": "1.1.3",
"gulp-terser": "2.1.0" "gulp-terser": "2.1.0",
"js-yaml": "4.1.0"
}, },
"devDependencies": { "devDependencies": {
"@redocly/openapi-core": "1.0.0-beta.54", "@redocly/openapi-core": "1.0.0-beta.54",

View file

@ -31,6 +31,10 @@ module.exports = {
'before': true, 'before': true,
'after': true, 'after': true,
}], }],
'key-spacing': ['error', {
'beforeColon': false,
'afterColon': true,
}],
/* TODO: path alias使warn /* TODO: path alias使warn
'no-restricted-imports': ['warn', { 'no-restricted-imports': ['warn', {
'patterns': [ 'patterns': [
@ -47,10 +51,15 @@ module.exports = {
'no-async-promise-executor': ['off'], 'no-async-promise-executor': ['off'],
'no-useless-escape': ['off'], 'no-useless-escape': ['off'],
'no-multi-spaces': ['warn'], 'no-multi-spaces': ['warn'],
'no-multiple-empty-lines': ['error', { 'max': 1 }],
'no-control-regex': ['warn'], 'no-control-regex': ['warn'],
'no-empty': ['warn'], 'no-empty': ['warn'],
'no-inner-declarations': ['off'], 'no-inner-declarations': ['off'],
'no-sparse-arrays': ['off'], 'no-sparse-arrays': ['off'],
'nonblock-statement-body-position': ['error', 'beside'],
'object-curly-spacing': ['error', 'always'],
'space-infix-ops': ['error'],
'space-before-blocks': ['error', 'always'],
'@typescript-eslint/no-var-requires': ['warn'], '@typescript-eslint/no-var-requires': ['warn'],
'@typescript-eslint/no-inferrable-types': ['warn'], '@typescript-eslint/no-inferrable-types': ['warn'],
'@typescript-eslint/no-empty-function': ['off'], '@typescript-eslint/no-empty-function': ['off'],

View file

@ -82,8 +82,7 @@ export default class Reversi {
//#endregion //#endregion
// ゲームが始まった時点で片方の色の石しかないか、始まった時点で勝敗が決定するようなマップの場合がある // ゲームが始まった時点で片方の色の石しかないか、始まった時点で勝敗が決定するようなマップの場合がある
if (!this.canPutSomewhere(BLACK)) if (!this.canPutSomewhere(BLACK)) this.turn = this.canPutSomewhere(WHITE) ? WHITE : null;
this.turn = this.canPutSomewhere(WHITE) ? WHITE : null;
} }
/** /**
@ -226,11 +225,12 @@ export default class Reversi {
// 座標が指し示す位置がボード外に出たとき // 座標が指し示す位置がボード外に出たとき
if (this.opts.loopedBoard && this.transformXyToPos( if (this.opts.loopedBoard && this.transformXyToPos(
(x = ((x % this.mapWidth) + this.mapWidth) % this.mapWidth), (x = ((x % this.mapWidth) + this.mapWidth) % this.mapWidth),
(y = ((y % this.mapHeight) + this.mapHeight) % this.mapHeight)) === initPos) (y = ((y % this.mapHeight) + this.mapHeight) % this.mapHeight)) === initPos) {
// 盤面の境界でループし、自分が石を置く位置に戻ってきたとき、挟めるようにしている (ref: Test4のマップ) // 盤面の境界でループし、自分が石を置く位置に戻ってきたとき、挟めるようにしている (ref: Test4のマップ)
return found; return found;
else if (x === -1 || y === -1 || x === this.mapWidth || y === this.mapHeight) } else if (x === -1 || y === -1 || x === this.mapWidth || y === this.mapHeight) {
return []; // 挟めないことが確定 (盤面外に到達) return []; // 挟めないことが確定 (盤面外に到達)
}
const pos = this.transformXyToPos(x, y); const pos = this.transformXyToPos(x, y);
if (this.mapDataGet(pos) === 'null') return []; // 挟めないことが確定 (配置不可能なマスに到達) if (this.mapDataGet(pos) === 'null') return []; // 挟めないことが確定 (配置不可能なマスに到達)

View file

@ -54,7 +54,7 @@ export async function populateEmoji(emojiName: string, noteUserHost: string | nu
const queryOrNull = async () => (await Emojis.findOne({ const queryOrNull = async () => (await Emojis.findOne({
name, name,
host host,
})) || null; })) || null;
const emoji = await cache.fetch(`${name} ${host}`, queryOrNull); const emoji = await cache.fetch(`${name} ${host}`, queryOrNull);
@ -62,7 +62,7 @@ export async function populateEmoji(emojiName: string, noteUserHost: string | nu
if (emoji == null) return null; if (emoji == null) return null;
const isLocal = emoji.host == null; const isLocal = emoji.host == null;
const url = isLocal ? emoji.url : `${config.url}/proxy/image.png?${query({url: emoji.url})}`; const url = isLocal ? emoji.url : `${config.url}/proxy/image.png?${query({ url: emoji.url })}`;
return { return {
name: emojiName, name: emojiName,
@ -111,12 +111,12 @@ export async function prefetchEmojis(emojis: { name: string; host: string | null
for (const host of hosts) { for (const host of hosts) {
emojisQuery.push({ emojisQuery.push({
name: In(notCachedEmojis.filter(e => e.host === host).map(e => e.name)), name: In(notCachedEmojis.filter(e => e.host === host).map(e => e.name)),
host: host host: host,
}); });
} }
const _emojis = emojisQuery.length > 0 ? await Emojis.find({ const _emojis = emojisQuery.length > 0 ? await Emojis.find({
where: emojisQuery, where: emojisQuery,
select: ['name', 'host', 'url'] select: ['name', 'host', 'url'],
}) : []; }) : [];
for (const emoji of _emojis) { for (const emoji of _emojis) {
cache.set(`${emoji.name} ${emoji.host}`, emoji); cache.set(`${emoji.name} ${emoji.host}`, emoji);

View file

@ -1,3 +1,4 @@
/* eslint-disable key-spacing */
import { emojiRegex } from './emoji-regex'; import { emojiRegex } from './emoji-regex';
import { fetchMeta } from './fetch-meta'; import { fetchMeta } from './fetch-meta';
import { Emojis } from '@/models/index'; import { Emojis } from '@/models/index';

View file

@ -19,7 +19,7 @@ export function createSignedPost(args: { key: PrivateKey, url: string, body: str
const request: Request = { const request: Request = {
url: u.href, url: u.href,
method: 'POST', method: 'POST',
headers: objectAssignWithLcKey({ headers: objectAssignWithLcKey({
'Date': new Date().toUTCString(), 'Date': new Date().toUTCString(),
'Host': u.hostname, 'Host': u.hostname,
'Content-Type': 'application/activity+json', 'Content-Type': 'application/activity+json',
@ -43,7 +43,7 @@ export function createSignedGet(args: { key: PrivateKey, url: string, additional
const request: Request = { const request: Request = {
url: u.href, url: u.href,
method: 'GET', method: 'GET',
headers: objectAssignWithLcKey({ headers: objectAssignWithLcKey({
'Accept': 'application/activity+json, application/ld+json', 'Accept': 'application/activity+json, application/ld+json',
'Date': new Date().toUTCString(), 'Date': new Date().toUTCString(),
'Host': new URL(args.url).hostname, 'Host': new URL(args.url).hostname,
@ -66,7 +66,7 @@ function signToRequest(request: Request, key: PrivateKey, includeHeaders: string
const signatureHeader = `keyId="${key.keyId}",algorithm="rsa-sha256",headers="${includeHeaders.join(' ')}",signature="${signature}"`; const signatureHeader = `keyId="${key.keyId}",algorithm="rsa-sha256",headers="${includeHeaders.join(' ')}",signature="${signature}"`;
request.headers = objectAssignWithLcKey(request.headers, { request.headers = objectAssignWithLcKey(request.headers, {
Signature: signatureHeader Signature: signatureHeader,
}); });
return { return {

File diff suppressed because it is too large Load diff

View file

@ -163,7 +163,7 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise<Us
uri: person.id, uri: person.id,
tags, tags,
isBot, isBot,
isCat: (person as any).isCat === true isCat: (person as any).isCat === true,
})) as IRemoteUser; })) as IRemoteUser;
await transactionalEntityManager.save(new UserProfile({ await transactionalEntityManager.save(new UserProfile({
@ -173,14 +173,14 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise<Us
fields, fields,
birthday: bday ? bday[0] : null, birthday: bday ? bday[0] : null,
location: person['vcard:Address'] || null, location: person['vcard:Address'] || null,
userHost: host userHost: host,
})); }));
if (person.publicKey) { if (person.publicKey) {
await transactionalEntityManager.save(new UserPublickey({ await transactionalEntityManager.save(new UserPublickey({
userId: user.id, userId: user.id,
keyId: person.publicKey.id, keyId: person.publicKey.id,
keyPem: person.publicKey.publicKeyPem keyPem: person.publicKey.publicKeyPem,
})); }));
} }
}); });
@ -189,7 +189,7 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise<Us
if (isDuplicateKeyValueError(e)) { if (isDuplicateKeyValueError(e)) {
// /users/@a => /users/:id のように入力がaliasなときにエラーになることがあるのを対応 // /users/@a => /users/:id のように入力がaliasなときにエラーになることがあるのを対応
const u = await Users.findOne({ const u = await Users.findOne({
uri: person.id uri: person.id,
}); });
if (u) { if (u) {
@ -218,11 +218,11 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise<Us
//#region アバターとヘッダー画像をフェッチ //#region アバターとヘッダー画像をフェッチ
const [avatar, banner] = await Promise.all([ const [avatar, banner] = await Promise.all([
person.icon, person.icon,
person.image person.image,
].map(img => ].map(img =>
img == null img == null
? Promise.resolve(null) ? Promise.resolve(null)
: resolveImage(user!, img).catch(() => null) : resolveImage(user!, img).catch(() => null),
)); ));
const avatarId = avatar ? avatar.id : null; const avatarId = avatar ? avatar.id : null;
@ -258,7 +258,7 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise<Us
const emojiNames = emojis.map(emoji => emoji.name); const emojiNames = emojis.map(emoji => emoji.name);
await Users.update(user!.id, { await Users.update(user!.id, {
emojis: emojiNames emojis: emojiNames,
}); });
//#endregion //#endregion
@ -301,11 +301,11 @@ export async function updatePerson(uri: string, resolver?: Resolver | null, hint
// アバターとヘッダー画像をフェッチ // アバターとヘッダー画像をフェッチ
const [avatar, banner] = await Promise.all([ const [avatar, banner] = await Promise.all([
person.icon, person.icon,
person.image person.image,
].map(img => ].map(img =>
img == null img == null
? Promise.resolve(null) ? Promise.resolve(null)
: resolveImage(exist, img).catch(() => null) : resolveImage(exist, img).catch(() => null),
)); ));
// カスタム絵文字取得 // カスタム絵文字取得
@ -355,7 +355,7 @@ export async function updatePerson(uri: string, resolver?: Resolver | null, hint
if (person.publicKey) { if (person.publicKey) {
await UserPublickeys.update({ userId: exist.id }, { await UserPublickeys.update({ userId: exist.id }, {
keyId: person.publicKey.id, keyId: person.publicKey.id,
keyPem: person.publicKey.publicKeyPem keyPem: person.publicKey.publicKeyPem,
}); });
} }
@ -372,9 +372,9 @@ export async function updatePerson(uri: string, resolver?: Resolver | null, hint
// 該当ユーザーが既にフォロワーになっていた場合はFollowingもアップデートする // 該当ユーザーが既にフォロワーになっていた場合はFollowingもアップデートする
await Followings.update({ await Followings.update({
followerId: exist.id followerId: exist.id,
}, { }, {
followerSharedInbox: person.sharedInbox || (person.endpoints ? person.endpoints.sharedInbox : undefined) followerSharedInbox: person.sharedInbox || (person.endpoints ? person.endpoints.sharedInbox : undefined),
}); });
await updateFeatured(exist.id).catch(err => logger.error(err)); await updateFeatured(exist.id).catch(err => logger.error(err));
@ -411,8 +411,9 @@ const services: {
}; };
const $discord = (id: string, name: string) => { const $discord = (id: string, name: string) => {
if (typeof name !== 'string') if (typeof name !== 'string') {
name = 'unknown#0000'; name = 'unknown#0000';
}
const [username, discriminator] = name.split('#'); const [username, discriminator] = name.split('#');
return { id, username, discriminator }; return { id, username, discriminator };
}; };
@ -420,13 +421,15 @@ const $discord = (id: string, name: string) => {
function addService(target: { [x: string]: any }, source: IApPropertyValue) { function addService(target: { [x: string]: any }, source: IApPropertyValue) {
const service = services[source.name]; const service = services[source.name];
if (typeof source.value !== 'string') if (typeof source.value !== 'string') {
source.value = 'unknown'; source.value = 'unknown';
}
const [id, username] = source.value.split('@'); const [id, username] = source.value.split('@');
if (service) if (service) {
target[source.name.split(':')[2]] = service(id, username); target[source.name.split(':')[2]] = service(id, username);
}
} }
export function analyzeAttachments(attachments: IObject | IObject[] | undefined) { export function analyzeAttachments(attachments: IObject | IObject[] | undefined) {
@ -443,7 +446,7 @@ export function analyzeAttachments(attachments: IObject | IObject[] | undefined)
} else { } else {
fields.push({ fields.push({
name: attachment.name, name: attachment.name,
value: fromHtml(attachment.value) value: fromHtml(attachment.value),
}); });
} }
} }
@ -487,7 +490,7 @@ export async function updateFeatured(userId: User['id']) {
id: genId(new Date(Date.now() + td)), id: genId(new Date(Date.now() + td)),
createdAt: new Date(), createdAt: new Date(),
userId: user.id, userId: user.id,
noteId: note!.id noteId: note!.id,
}); });
} }
}); });

View file

@ -8,7 +8,7 @@ export default async function renderQuestion(user: { id: User['id'] }, note: Not
type: 'Question', type: 'Question',
id: `${config.url}/questions/${note.id}`, id: `${config.url}/questions/${note.id}`,
actor: `${config.url}/users/${user.id}`, actor: `${config.url}/users/${user.id}`,
content: note.text || '', content: note.text || '',
[poll.multiple ? 'anyOf' : 'oneOf']: poll.choices.map((text, i) => ({ [poll.multiple ? 'anyOf' : 'oneOf']: poll.choices.map((text, i) => ({
name: text, name: text,
_misskey_votes: poll.votes[i], _misskey_votes: poll.votes[i],

View file

@ -6,7 +6,7 @@ const ECC_PRELUDE = Buffer.from([0x04]);
const NULL_BYTE = Buffer.from([0]); const NULL_BYTE = Buffer.from([0]);
const PEM_PRELUDE = Buffer.from( const PEM_PRELUDE = Buffer.from(
'3059301306072a8648ce3d020106082a8648ce3d030107034200', '3059301306072a8648ce3d020106082a8648ce3d030107034200',
'hex' 'hex',
); );
// Android Safetynet attestations are signed with this cert: // Android Safetynet attestations are signed with this cert:
@ -68,7 +68,7 @@ function verifyCertificateChain(certificates: string[]) {
const signatureHex = certificate.getSignatureValueHex(); const signatureHex = certificate.getSignatureValueHex();
// Verify against CA // Verify against CA
const Signature = new jsrsasign.KJUR.crypto.Signature({alg: algorithm}); const Signature = new jsrsasign.KJUR.crypto.Signature({ alg: algorithm });
Signature.init(CACert); Signature.init(CACert);
Signature.updateHex(certStruct); Signature.updateHex(certStruct);
valid = valid && !!Signature.verify(signatureHex); // true if CA signed the certificate valid = valid && !!Signature.verify(signatureHex); // true if CA signed the certificate
@ -134,7 +134,7 @@ export function verifyLogin({
const verificationData = Buffer.concat( const verificationData = Buffer.concat(
[authenticatorData, hash(clientDataJSON)], [authenticatorData, hash(clientDataJSON)],
32 + authenticatorData.length 32 + authenticatorData.length,
); );
return crypto return crypto
@ -145,7 +145,7 @@ export function verifyLogin({
export const procedures = { export const procedures = {
none: { none: {
verify({publicKey}: {publicKey: Map<number, Buffer>}) { verify({ publicKey }: {publicKey: Map<number, Buffer>}) {
const negTwo = publicKey.get(-2); const negTwo = publicKey.get(-2);
if (!negTwo || negTwo.length != 32) { if (!negTwo || negTwo.length != 32) {
@ -158,14 +158,14 @@ export const procedures = {
const publicKeyU2F = Buffer.concat( const publicKeyU2F = Buffer.concat(
[ECC_PRELUDE, negTwo, negThree], [ECC_PRELUDE, negTwo, negThree],
1 + 32 + 32 1 + 32 + 32,
); );
return { return {
publicKey: publicKeyU2F, publicKey: publicKeyU2F,
valid: true valid: true,
}; };
} },
}, },
'android-key': { 'android-key': {
verify({ verify({
@ -174,7 +174,7 @@ export const procedures = {
clientDataHash, clientDataHash,
publicKey, publicKey,
rpIdHash, rpIdHash,
credentialId credentialId,
}: { }: {
attStmt: any, attStmt: any,
authenticatorData: Buffer, authenticatorData: Buffer,
@ -189,7 +189,7 @@ export const procedures = {
const verificationData = Buffer.concat([ const verificationData = Buffer.concat([
authenticatorData, authenticatorData,
clientDataHash clientDataHash,
]); ]);
const attCert: Buffer = attStmt.x5c[0]; const attCert: Buffer = attStmt.x5c[0];
@ -206,7 +206,7 @@ export const procedures = {
const publicKeyData = Buffer.concat( const publicKeyData = Buffer.concat(
[ECC_PRELUDE, negTwo, negThree], [ECC_PRELUDE, negTwo, negThree],
1 + 32 + 32 1 + 32 + 32,
); );
if (!attCert.equals(publicKeyData)) { if (!attCert.equals(publicKeyData)) {
@ -222,9 +222,9 @@ export const procedures = {
return { return {
valid: isValid, valid: isValid,
publicKey: publicKeyData publicKey: publicKeyData,
}; };
} },
}, },
// what a stupid attestation // what a stupid attestation
'android-safetynet': { 'android-safetynet': {
@ -234,7 +234,7 @@ export const procedures = {
clientDataHash, clientDataHash,
publicKey, publicKey,
rpIdHash, rpIdHash,
credentialId credentialId,
}: { }: {
attStmt: any, attStmt: any,
authenticatorData: Buffer, authenticatorData: Buffer,
@ -244,14 +244,14 @@ export const procedures = {
credentialId: Buffer, credentialId: Buffer,
}) { }) {
const verificationData = hash( const verificationData = hash(
Buffer.concat([authenticatorData, clientDataHash]) Buffer.concat([authenticatorData, clientDataHash]),
); );
const jwsParts = attStmt.response.toString('utf-8').split('.'); const jwsParts = attStmt.response.toString('utf-8').split('.');
const header = JSON.parse(base64URLDecode(jwsParts[0]).toString('utf-8')); const header = JSON.parse(base64URLDecode(jwsParts[0]).toString('utf-8'));
const response = JSON.parse( const response = JSON.parse(
base64URLDecode(jwsParts[1]).toString('utf-8') base64URLDecode(jwsParts[1]).toString('utf-8'),
); );
const signature = jwsParts[2]; const signature = jwsParts[2];
@ -273,7 +273,7 @@ export const procedures = {
const signatureBase = Buffer.from( const signatureBase = Buffer.from(
jwsParts[0] + '.' + jwsParts[1], jwsParts[0] + '.' + jwsParts[1],
'utf-8' 'utf-8',
); );
const valid = crypto const valid = crypto
@ -293,13 +293,13 @@ export const procedures = {
const publicKeyData = Buffer.concat( const publicKeyData = Buffer.concat(
[ECC_PRELUDE, negTwo, negThree], [ECC_PRELUDE, negTwo, negThree],
1 + 32 + 32 1 + 32 + 32,
); );
return { return {
valid, valid,
publicKey: publicKeyData publicKey: publicKeyData,
}; };
} },
}, },
packed: { packed: {
verify({ verify({
@ -308,7 +308,7 @@ export const procedures = {
clientDataHash, clientDataHash,
publicKey, publicKey,
rpIdHash, rpIdHash,
credentialId credentialId,
}: { }: {
attStmt: any, attStmt: any,
authenticatorData: Buffer, authenticatorData: Buffer,
@ -319,7 +319,7 @@ export const procedures = {
}) { }) {
const verificationData = Buffer.concat([ const verificationData = Buffer.concat([
authenticatorData, authenticatorData,
clientDataHash clientDataHash,
]); ]);
if (attStmt.x5c) { if (attStmt.x5c) {
@ -342,12 +342,12 @@ export const procedures = {
const publicKeyData = Buffer.concat( const publicKeyData = Buffer.concat(
[ECC_PRELUDE, negTwo, negThree], [ECC_PRELUDE, negTwo, negThree],
1 + 32 + 32 1 + 32 + 32,
); );
return { return {
valid: validSignature, valid: validSignature,
publicKey: publicKeyData publicKey: publicKeyData,
}; };
} else if (attStmt.ecdaaKeyId) { } else if (attStmt.ecdaaKeyId) {
// https://fidoalliance.org/specs/fido-v2.0-id-20180227/fido-ecdaa-algorithm-v2.0-id-20180227.html#ecdaa-verify-operation // https://fidoalliance.org/specs/fido-v2.0-id-20180227/fido-ecdaa-algorithm-v2.0-id-20180227.html#ecdaa-verify-operation
@ -357,7 +357,7 @@ export const procedures = {
throw new Error('self attestation is not supported'); throw new Error('self attestation is not supported');
} }
} },
}, },
'fido-u2f': { 'fido-u2f': {
@ -367,7 +367,7 @@ export const procedures = {
clientDataHash, clientDataHash,
publicKey, publicKey,
rpIdHash, rpIdHash,
credentialId credentialId,
}: { }: {
attStmt: any, attStmt: any,
authenticatorData: Buffer, authenticatorData: Buffer,
@ -397,7 +397,7 @@ export const procedures = {
const publicKeyU2F = Buffer.concat( const publicKeyU2F = Buffer.concat(
[ECC_PRELUDE, negTwo, negThree], [ECC_PRELUDE, negTwo, negThree],
1 + 32 + 32 1 + 32 + 32,
); );
const verificationData = Buffer.concat([ const verificationData = Buffer.concat([
@ -405,7 +405,7 @@ export const procedures = {
rpIdHash, rpIdHash,
clientDataHash, clientDataHash,
credentialId, credentialId,
publicKeyU2F publicKeyU2F,
]); ]);
const validSignature = crypto const validSignature = crypto
@ -415,8 +415,8 @@ export const procedures = {
return { return {
valid: validSignature, valid: validSignature,
publicKey: publicKeyU2F publicKey: publicKeyU2F,
}; };
} },
} },
}; };

View file

@ -244,8 +244,9 @@ export default define(meta, async (ps, user) => {
if (ps.poll) { if (ps.poll) {
if (typeof ps.poll.expiresAt === 'number') { if (typeof ps.poll.expiresAt === 'number') {
if (ps.poll.expiresAt < Date.now()) if (ps.poll.expiresAt < Date.now()) {
throw new ApiError(meta.errors.cannotCreateAlreadyExpiredPoll); throw new ApiError(meta.errors.cannotCreateAlreadyExpiredPoll);
}
} else if (typeof ps.poll.expiredAfter === 'number') { } else if (typeof ps.poll.expiredAfter === 'number') {
ps.poll.expiresAt = Date.now() + ps.poll.expiredAfter; ps.poll.expiresAt = Date.now() + ps.poll.expiredAfter;
} }

View file

@ -112,8 +112,9 @@ export default define(meta, async (ps, user) => {
if (exist.length) { if (exist.length) {
if (poll.multiple) { if (poll.multiple) {
if (exist.some(x => x.choice == ps.choice)) if (exist.some(x => x.choice == ps.choice)) {
throw new ApiError(meta.errors.alreadyVoted); throw new ApiError(meta.errors.alreadyVoted);
}
} else { } else {
throw new ApiError(meta.errors.alreadyVoted); throw new ApiError(meta.errors.alreadyVoted);
} }

View file

@ -42,7 +42,7 @@ router.get('/disconnect/github', async ctx => {
const user = await Users.findOneOrFail({ const user = await Users.findOneOrFail({
host: null, host: null,
token: userToken token: userToken,
}); });
const profile = await UserProfiles.findOneOrFail(user.id); const profile = await UserProfiles.findOneOrFail(user.id);
@ -58,7 +58,7 @@ router.get('/disconnect/github', async ctx => {
// Publish i updated event // Publish i updated event
publishMainStream(user.id, 'meUpdated', await Users.pack(user, user, { publishMainStream(user.id, 'meUpdated', await Users.pack(user, user, {
detail: true, detail: true,
includeSecrets: true includeSecrets: true,
})); }));
}); });
@ -209,12 +209,13 @@ router.get('/gh/cb', async ctx => {
code, code,
{ redirect_uri }, { redirect_uri },
(err, accessToken, refresh, result) => { (err, accessToken, refresh, result) => {
if (err) if (err) {
rej(err); rej(err);
else if (result.error) } else if (result.error) {
rej(result.error); rej(result.error);
else } else {
res({ accessToken }); res({ accessToken });
}
})); }));
const { login, id } = await getJson('https://api.github.com/user', 'application/vnd.github.v3+json', 10 * 1000, { const { login, id } = await getJson('https://api.github.com/user', 'application/vnd.github.v3+json', 10 * 1000, {

View file

@ -45,8 +45,6 @@
localStorage.setItem('lang', lang); localStorage.setItem('lang', lang);
localStorage.setItem('locale', await res.text()); localStorage.setItem('locale', await res.text());
localStorage.setItem('localeVersion', v); localStorage.setItem('localeVersion', v);
} else if (localeOutdated) {
// nop
} else { } else {
await checkUpdate(); await checkUpdate();
renderError('LOCALE_FETCH_FAILED'); renderError('LOCALE_FETCH_FAILED');

View file

@ -41,8 +41,8 @@ router.get('/.well-known/host-meta', async ctx => {
ctx.set('Content-Type', xrd); ctx.set('Content-Type', xrd);
ctx.body = XRD({ element: 'Link', attributes: { ctx.body = XRD({ element: 'Link', attributes: {
type: xrd, type: xrd,
template: `${config.url}${webFingerPath}?resource={uri}` template: `${config.url}${webFingerPath}?resource={uri}`,
}}); } });
}); });
router.get('/.well-known/host-meta.json', async ctx => { router.get('/.well-known/host-meta.json', async ctx => {
@ -51,8 +51,8 @@ router.get('/.well-known/host-meta.json', async ctx => {
links: [{ links: [{
rel: 'lrdd', rel: 'lrdd',
type: jrd, type: jrd,
template: `${config.url}${webFingerPath}?resource={uri}` template: `${config.url}${webFingerPath}?resource={uri}`,
}] }],
}; };
}); });

View file

@ -106,6 +106,7 @@
"punycode": "2.1.1", "punycode": "2.1.1",
"pureimage": "0.3.5", "pureimage": "0.3.5",
"qrcode": "1.4.4", "qrcode": "1.4.4",
"querystring": "0.2.1",
"random-seed": "0.3.0", "random-seed": "0.3.0",
"ratelimiter": "3.4.1", "ratelimiter": "3.4.1",
"reflect-metadata": "0.1.13", "reflect-metadata": "0.1.13",

View file

@ -6,21 +6,11 @@
<div class="name">{{ reaction.replace('@.', '') }}</div> <div class="name">{{ reaction.replace('@.', '') }}</div>
</div> </div>
<div class="users"> <div class="users">
<template v-if="users.length <= 10"> <div class="user" v-for="u in users" :key="u.id">
<b v-for="u in users" :key="u.id" style="margin-right: 12px;"> <MkAvatar class="avatar" :user="u"/>
<MkAvatar :user="u" style="width: 24px; height: 24px; margin-right: 2px;"/> <MkUserName class="name" :user="u" :nowrap="true"/>
<br/> </div>
<MkUserName :user="u" :nowrap="false" style="line-height: 24px;"/> <div v-if="users.length > 10" class="omitted">+{{ count - 10 }}</div>
</b>
</template>
<template v-if="10 < users.length">
<b v-for="u in users" :key="u.id" style="margin-right: 12px;">
<MkAvatar :user="u" style="width: 24px; height: 24px; margin-right: 2px;"/>
<br/>
<MkUserName :user="u" :nowrap="false" style="line-height: 24px;"/>
</b>
<span slot="omitted">+{{ count - 10 }}</span>
</template>
</div> </div>
</div> </div>
</MkTooltip> </MkTooltip>
@ -81,13 +71,31 @@ export default defineComponent({
} }
> .users { > .users {
display: flex;
flex: 1; flex: 1;
min-width: 0; min-width: 0;
font-size: 0.9em; font-size: 0.9em;
border-left: solid 0.5px var(--divider); border-left: solid 0.5px var(--divider);
padding-left: 10px; padding-left: 10px;
margin-left: 10px; margin-left: 10px;
margin-right: 14px;
text-align: left;
> .user {
line-height: 24px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
&:not(:last-child) {
margin-bottom: 3px;
}
> .avatar {
width: 24px;
height: 24px;
margin-right: 3px;
}
}
} }
} }
</style> </style>

View file

@ -1,11 +1,11 @@
<template> <template>
<MkTooltip :source="source" ref="tooltip" @closed="$emit('closed')" :max-width="340"> <MkTooltip :source="source" ref="tooltip" @closed="$emit('closed')" :max-width="250">
<div class="renoteTooltip"> <div class="beaffaef">
<b v-for="u in users" :key="u.id"> <div class="user" v-for="u in users" :key="u.id">
<MkAvatar :user="u" style="width: 24px; height: 24px;"/><br/> <MkAvatar class="avatar" :user="u"/>
<MkUserName :user="u" :nowrap="false" style="line-height: 24px;"/> <MkUserName class="name" :user="u" :nowrap="true"/>
</b> </div>
<span v-if="users.length < count" slot="omitted">+{{ count - users.length }}</span> <div v-if="users.length < count" class="omitted">+{{ count - users.length }}</div>
</div> </div>
</MkTooltip> </MkTooltip>
</template> </template>
@ -36,11 +36,25 @@ export default defineComponent({
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.renoteTooltip { .beaffaef {
display: flex;
flex: 1;
min-width: 0;
font-size: 0.9em; font-size: 0.9em;
gap: 12px; text-align: left;
> .user {
line-height: 24px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
&:not(:last-child) {
margin-bottom: 3px;
}
> .avatar {
width: 24px;
height: 24px;
margin-right: 3px;
}
}
} }
</style> </style>

View file

@ -42,10 +42,6 @@ import { getAccountFromId } from '@/scripts/get-account-from-id';
console.info(`Misskey v${version}`); console.info(`Misskey v${version}`);
// boot.jsのやつを解除
window.onerror = null;
window.onunhandledrejection = null;
if (_DEV_) { if (_DEV_) {
console.warn('Development mode!!!'); console.warn('Development mode!!!');
@ -223,6 +219,10 @@ const rootEl = document.createElement('div');
document.body.appendChild(rootEl); document.body.appendChild(rootEl);
app.mount(rootEl); app.mount(rootEl);
// boot.jsのやつを解除
window.onerror = null;
window.onunhandledrejection = null;
reactionPicker.init(); reactionPicker.init();
if (splash) { if (splash) {

View file

@ -45,6 +45,15 @@ export default defineComponent({
methods: { methods: {
async deleteAccount() { async deleteAccount() {
{
const { canceled } = await os.dialog({
type: 'warning',
text: this.$ts.deleteAccountConfirm,
showCancelButton: true
});
if (canceled) return;
}
const { canceled, result: password } = await os.dialog({ const { canceled, result: password } = await os.dialog({
title: this.$ts.password, title: this.$ts.password,
input: { input: {

View file

@ -5237,6 +5237,11 @@ querystring@0.2.0:
resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620"
integrity sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA= integrity sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=
querystring@0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.1.tgz#40d77615bb09d16902a85c3e38aa8b5ed761c2dd"
integrity sha512-wkvS7mL/JMugcup3/rMitHmd9ecIGd2lhFhK9N3UUQ450h66d1r3Y9nvXzQAW1Lq+wyx61k/1pfKS5KuKiyEbg==
quick-lru@^5.1.1: quick-lru@^5.1.1:
version "5.1.1" version "5.1.1"
resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-5.1.1.tgz#366493e6b3e42a3a6885e2e99d18f80fb7a8c932" resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-5.1.1.tgz#366493e6b3e42a3a6885e2e99d18f80fb7a8c932"

View file

@ -17,12 +17,6 @@ const execa = require('execa');
stderr: process.stderr, stderr: process.stderr,
}); });
await execa('npm', ['run', 'build'], {
cwd: __dirname + '/../packages/client',
stdout: process.stdout,
stderr: process.stderr,
});
console.log('build finishing ...'); console.log('build finishing ...');
await execa('npm', ['run', 'gulp'], { await execa('npm', ['run', 'gulp'], {

View file

@ -323,6 +323,11 @@ argparse@^1.0.7:
dependencies: dependencies:
sprintf-js "~1.0.2" sprintf-js "~1.0.2"
argparse@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38"
integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==
arr-diff@^4.0.0: arr-diff@^4.0.0:
version "4.0.0" version "4.0.0"
resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520" resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520"
@ -2300,6 +2305,13 @@ js-levenshtein@^1.1.6:
resolved "https://registry.yarnpkg.com/js-levenshtein/-/js-levenshtein-1.1.6.tgz#c6cee58eb3550372df8deb85fad5ce66ce01d59d" resolved "https://registry.yarnpkg.com/js-levenshtein/-/js-levenshtein-1.1.6.tgz#c6cee58eb3550372df8deb85fad5ce66ce01d59d"
integrity sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g== integrity sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==
js-yaml@4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602"
integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==
dependencies:
argparse "^2.0.1"
js-yaml@^3.14.1: js-yaml@^3.14.1:
version "3.14.1" version "3.14.1"
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537"