mirror of
https://git.joinsharkey.org/Sharkey/Sharkey.git
synced 2024-11-23 02:43:09 +02:00
Compare commits
15 commits
9b9843b1bd
...
53ceb09851
Author | SHA1 | Date | |
---|---|---|---|
|
53ceb09851 | ||
|
126248e58d | ||
|
074de82bf7 | ||
|
58bc8f2c10 | ||
|
94aed953b5 | ||
|
aa7035a35a | ||
|
45eab01fc4 | ||
|
71bcd76cc5 | ||
|
d003c3ec1f | ||
|
b918f38ec2 | ||
|
cdb82c0ade | ||
|
6826e43ad7 | ||
|
ff189b1952 | ||
|
43544a6479 | ||
|
03464cc379 |
12 changed files with 91 additions and 21 deletions
|
@ -6,8 +6,11 @@ When using a service with Sharkey, there are several important points to keep in
|
||||||
|
|
||||||
2. Even for posts made in private, there is no guarantee that the recipient's server will treat them as private in the same way. Please exercise caution when posting personal or confidential information. (Again, this applies to the internet in general.)
|
2. Even for posts made in private, there is no guarantee that the recipient's server will treat them as private in the same way. Please exercise caution when posting personal or confidential information. (Again, this applies to the internet in general.)
|
||||||
|
|
||||||
3. Account deletion can be a resource-intensive process and may take a long time. In cases with a lot of uploaded data, it may even be impossible to delete an account.
|
3. The "Drive" feature is NOT secure cloud storage. This feature exists for easier managing of your uploaded files.
|
||||||
|
Any data uploaded, whether shared via post or not, will be publicly accessible. Please use 3rd party cloud storage providers if you need to upload data with sensitive information of any kind.
|
||||||
|
|
||||||
4. Please disable ad blockers. Some servers may rely on advertising revenue to cover operating costs. Additionally, ad blockers can mistakenly block content and features unrelated to ads, potentially causing issues with the client's functionality and preventing normal use of Sharkey. Therefore, we recommend turning off ad blockers and similar features when using Sharkey.
|
4. Account deletion can be a resource-intensive process and may take a long time. In cases with a lot of uploaded data, it may even be impossible to delete an account.
|
||||||
|
|
||||||
Please understand these points and enjoy using the service.
|
5. Please disable ad blockers. Some servers may rely on advertising revenue to cover operating costs. Additionally, ad blockers can mistakenly block content and features unrelated to ads, potentially causing issues with the client's functionality and preventing normal use of Sharkey. Therefore, we recommend turning off ad blockers and similar features when using Sharkey.
|
||||||
|
|
||||||
|
Please understand these points and enjoy using the service.
|
||||||
|
|
|
@ -11,7 +11,11 @@ export default new DataSource({
|
||||||
username: config.db.user,
|
username: config.db.user,
|
||||||
password: config.db.pass,
|
password: config.db.pass,
|
||||||
database: config.db.db,
|
database: config.db.db,
|
||||||
extra: config.db.extra,
|
extra: {
|
||||||
|
...config.db.extra,
|
||||||
|
// migrations may be very slow, give them longer to run (that 10*1000 comes from postgres.ts)
|
||||||
|
statement_timeout: (config.db.extra?.statement_timeout ?? 1000 * 10) * 10,
|
||||||
|
},
|
||||||
entities: entities,
|
entities: entities,
|
||||||
migrations: ['migration/*.js'],
|
migrations: ['migration/*.js'],
|
||||||
});
|
});
|
||||||
|
|
|
@ -15,6 +15,7 @@ import type { Config } from '@/config.js';
|
||||||
import { StatusError } from '@/misc/status-error.js';
|
import { StatusError } from '@/misc/status-error.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import { validateContentTypeSetAsActivityPub } from '@/core/activitypub/misc/validator.js';
|
import { validateContentTypeSetAsActivityPub } from '@/core/activitypub/misc/validator.js';
|
||||||
|
import { assertActivityMatchesUrls } from '@/core/activitypub/misc/check-against-url.js';
|
||||||
import type { IObject } from '@/core/activitypub/type.js';
|
import type { IObject } from '@/core/activitypub/type.js';
|
||||||
import type { Response } from 'node-fetch';
|
import type { Response } from 'node-fetch';
|
||||||
import type { URL } from 'node:url';
|
import type { URL } from 'node:url';
|
||||||
|
@ -125,7 +126,12 @@ export class HttpRequestService {
|
||||||
validators: [validateContentTypeSetAsActivityPub],
|
validators: [validateContentTypeSetAsActivityPub],
|
||||||
});
|
});
|
||||||
|
|
||||||
return await res.json() as IObject;
|
const finalUrl = res.url; // redirects may have been involved
|
||||||
|
const activity = await res.json() as IObject;
|
||||||
|
|
||||||
|
assertActivityMatchesUrls(activity, [url, finalUrl]);
|
||||||
|
|
||||||
|
return activity;
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
|
|
|
@ -86,7 +86,7 @@ export class UtilityService {
|
||||||
@bindThis
|
@bindThis
|
||||||
public extractDbHost(uri: string): string {
|
public extractDbHost(uri: string): string {
|
||||||
const url = new URL(uri);
|
const url = new URL(uri);
|
||||||
return this.toPuny(url.hostname);
|
return this.toPuny(url.host);
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
|
@ -99,4 +99,11 @@ export class UtilityService {
|
||||||
if (host == null) return null;
|
if (host == null) return null;
|
||||||
return toASCII(host.toLowerCase());
|
return toASCII(host.toLowerCase());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
public punyHost(url: string): string {
|
||||||
|
const urlObj = new URL(url);
|
||||||
|
const host = `${this.toPuny(urlObj.hostname)}${urlObj.port.length > 0 ? ':' + urlObj.port : ''}`;
|
||||||
|
return host;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,9 @@ import { HttpRequestService } from '@/core/HttpRequestService.js';
|
||||||
import { LoggerService } from '@/core/LoggerService.js';
|
import { LoggerService } from '@/core/LoggerService.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import type Logger from '@/logger.js';
|
import type Logger from '@/logger.js';
|
||||||
|
import type { IObject } from './type.js';
|
||||||
import { validateContentTypeSetAsActivityPub } from '@/core/activitypub/misc/validator.js';
|
import { validateContentTypeSetAsActivityPub } from '@/core/activitypub/misc/validator.js';
|
||||||
|
import { assertActivityMatchesUrls } from '@/core/activitypub/misc/check-against-url.js';
|
||||||
|
|
||||||
type Request = {
|
type Request = {
|
||||||
url: string;
|
url: string;
|
||||||
|
@ -201,6 +203,11 @@ export class ApRequestService {
|
||||||
validators: [validateContentTypeSetAsActivityPub],
|
validators: [validateContentTypeSetAsActivityPub],
|
||||||
});
|
});
|
||||||
|
|
||||||
return await res.json();
|
const finalUrl = res.url; // redirects may have been involved
|
||||||
|
const activity = await res.json() as IObject;
|
||||||
|
|
||||||
|
assertActivityMatchesUrls(activity, [url, finalUrl]);
|
||||||
|
|
||||||
|
return activity;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -115,6 +115,14 @@ export class Resolver {
|
||||||
throw new Error('invalid response');
|
throw new Error('invalid response');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HttpRequestService / ApRequestService have already checked that
|
||||||
|
// `object.id` or `object.url` matches the URL used to fetch the
|
||||||
|
// object after redirects; here we double-check that no redirects
|
||||||
|
// bounced between hosts
|
||||||
|
if (object.id && (this.utilityService.punyHost(object.id) !== this.utilityService.punyHost(value))) {
|
||||||
|
throw new Error(`invalid AP object ${value}: id ${object.id} has different host`);
|
||||||
|
}
|
||||||
|
|
||||||
return object;
|
return object;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: dakkar and sharkey-project
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
import type { IObject } from '../type.js';
|
||||||
|
|
||||||
|
export function assertActivityMatchesUrls(activity: IObject, urls: string[]) {
|
||||||
|
const idOk = activity.id !== undefined && urls.includes(activity.id);
|
||||||
|
|
||||||
|
// technically `activity.url` could be an `ApObject = IObject |
|
||||||
|
// string | (IObject | string)[]`, but if it's a complicated thing
|
||||||
|
// and the `activity.id` doesn't match, I think we're fine
|
||||||
|
// rejecting the activity
|
||||||
|
const urlOk = typeof(activity.url) === 'string' && urls.includes(activity.url);
|
||||||
|
|
||||||
|
if (!idOk && !urlOk) {
|
||||||
|
throw new Error(`bad Activity: neither id(${activity?.id}) nor url(${activity?.url}) match location(${urls})`);
|
||||||
|
}
|
||||||
|
}
|
|
@ -127,12 +127,6 @@ export class ApPersonService implements OnModuleInit {
|
||||||
this.logger = this.apLoggerService.logger;
|
this.logger = this.apLoggerService.logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
private punyHost(url: string): string {
|
|
||||||
const urlObj = new URL(url);
|
|
||||||
const host = `${this.utilityService.toPuny(urlObj.hostname)}${urlObj.port.length > 0 ? ':' + urlObj.port : ''}`;
|
|
||||||
return host;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate and convert to actor object
|
* Validate and convert to actor object
|
||||||
* @param x Fetched object
|
* @param x Fetched object
|
||||||
|
@ -140,7 +134,7 @@ export class ApPersonService implements OnModuleInit {
|
||||||
*/
|
*/
|
||||||
@bindThis
|
@bindThis
|
||||||
private validateActor(x: IObject, uri: string): IActor {
|
private validateActor(x: IObject, uri: string): IActor {
|
||||||
const expectHost = this.punyHost(uri);
|
const expectHost = this.utilityService.punyHost(uri);
|
||||||
|
|
||||||
if (!isActor(x)) {
|
if (!isActor(x)) {
|
||||||
throw new Error(`invalid Actor type '${x.type}'`);
|
throw new Error(`invalid Actor type '${x.type}'`);
|
||||||
|
@ -154,6 +148,19 @@ export class ApPersonService implements OnModuleInit {
|
||||||
throw new Error('invalid Actor: wrong inbox');
|
throw new Error('invalid Actor: wrong inbox');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.utilityService.punyHost(x.inbox) !== expectHost) {
|
||||||
|
throw new Error('invalid Actor: inbox has different host');
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const collection of ['outbox', 'followers', 'following'] as (keyof IActor)[]) {
|
||||||
|
const collectionUri = (x as IActor)[collection];
|
||||||
|
if (typeof collectionUri === 'string' && collectionUri.length > 0) {
|
||||||
|
if (this.utilityService.punyHost(collectionUri) !== expectHost) {
|
||||||
|
throw new Error(`invalid Actor: ${collection} has different host`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!(typeof x.preferredUsername === 'string' && x.preferredUsername.length > 0 && x.preferredUsername.length <= 128 && /^\w([\w-.]*\w)?$/.test(x.preferredUsername))) {
|
if (!(typeof x.preferredUsername === 'string' && x.preferredUsername.length > 0 && x.preferredUsername.length <= 128 && /^\w([\w-.]*\w)?$/.test(x.preferredUsername))) {
|
||||||
throw new Error('invalid Actor: wrong username');
|
throw new Error('invalid Actor: wrong username');
|
||||||
}
|
}
|
||||||
|
@ -177,7 +184,7 @@ export class ApPersonService implements OnModuleInit {
|
||||||
x.summary = truncate(x.summary, summaryLength);
|
x.summary = truncate(x.summary, summaryLength);
|
||||||
}
|
}
|
||||||
|
|
||||||
const idHost = this.punyHost(x.id);
|
const idHost = this.utilityService.punyHost(x.id);
|
||||||
if (idHost !== expectHost) {
|
if (idHost !== expectHost) {
|
||||||
throw new Error('invalid Actor: id has different host');
|
throw new Error('invalid Actor: id has different host');
|
||||||
}
|
}
|
||||||
|
@ -187,7 +194,7 @@ export class ApPersonService implements OnModuleInit {
|
||||||
throw new Error('invalid Actor: publicKey.id is not a string');
|
throw new Error('invalid Actor: publicKey.id is not a string');
|
||||||
}
|
}
|
||||||
|
|
||||||
const publicKeyIdHost = this.punyHost(x.publicKey.id);
|
const publicKeyIdHost = this.utilityService.punyHost(x.publicKey.id);
|
||||||
if (publicKeyIdHost !== expectHost) {
|
if (publicKeyIdHost !== expectHost) {
|
||||||
throw new Error('invalid Actor: publicKey.id has different host');
|
throw new Error('invalid Actor: publicKey.id has different host');
|
||||||
}
|
}
|
||||||
|
@ -286,7 +293,7 @@ export class ApPersonService implements OnModuleInit {
|
||||||
|
|
||||||
this.logger.info(`Creating the Person: ${person.id}`);
|
this.logger.info(`Creating the Person: ${person.id}`);
|
||||||
|
|
||||||
const host = this.punyHost(object.id);
|
const host = this.utilityService.punyHost(object.id);
|
||||||
|
|
||||||
const fields = this.analyzeAttachments(person.attachment ?? []);
|
const fields = this.analyzeAttachments(person.attachment ?? []);
|
||||||
|
|
||||||
|
|
|
@ -113,8 +113,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
@bindThis
|
@bindThis
|
||||||
private async fetchAny(uri: string, me: MiLocalUser | null | undefined): Promise<SchemaType<typeof meta['res']> | null> {
|
private async fetchAny(uri: string, me: MiLocalUser | null | undefined): Promise<SchemaType<typeof meta['res']> | null> {
|
||||||
// ブロックしてたら中断
|
// ブロックしてたら中断
|
||||||
|
const host = this.utilityService.extractDbHost(uri);
|
||||||
const fetchedMeta = await this.metaService.fetch();
|
const fetchedMeta = await this.metaService.fetch();
|
||||||
if (this.utilityService.isBlockedHost(fetchedMeta.blockedHosts, this.utilityService.extractDbHost(uri))) return null;
|
if (this.utilityService.isBlockedHost(fetchedMeta.blockedHosts, host)) return null;
|
||||||
|
|
||||||
let local = await this.mergePack(me, ...await Promise.all([
|
let local = await this.mergePack(me, ...await Promise.all([
|
||||||
this.apDbResolverService.getUserFromApId(uri),
|
this.apDbResolverService.getUserFromApId(uri),
|
||||||
|
@ -122,6 +123,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
]));
|
]));
|
||||||
if (local != null) return local;
|
if (local != null) return local;
|
||||||
|
|
||||||
|
// local object, not found in db? fail
|
||||||
|
if (this.utilityService.isSelfHost(host)) return null;
|
||||||
|
|
||||||
// リモートから一旦オブジェクトフェッチ
|
// リモートから一旦オブジェクトフェッチ
|
||||||
const resolver = this.apResolverService.createResolver();
|
const resolver = this.apResolverService.createResolver();
|
||||||
const object = await resolver.resolve(uri) as any;
|
const object = await resolver.resolve(uri) as any;
|
||||||
|
|
|
@ -43,6 +43,7 @@ export async function signout() {
|
||||||
waiting();
|
waiting();
|
||||||
miLocalStorage.removeItem('account');
|
miLocalStorage.removeItem('account');
|
||||||
await removeAccount($i.id);
|
await removeAccount($i.id);
|
||||||
|
document.cookie = `token=; path=/; max-age=0${ location.protocol === 'https:' ? '; Secure' : ''}`;
|
||||||
const accounts = await getAccounts();
|
const accounts = await getAccounts();
|
||||||
|
|
||||||
//#region Remove service worker registration
|
//#region Remove service worker registration
|
||||||
|
@ -200,7 +201,7 @@ export async function login(token: Account['token'], redirect?: string) {
|
||||||
throw reason;
|
throw reason;
|
||||||
});
|
});
|
||||||
miLocalStorage.setItem('account', JSON.stringify(me));
|
miLocalStorage.setItem('account', JSON.stringify(me));
|
||||||
document.cookie = `token=${token}; path=/; max-age=31536000`; // bull dashboardの認証とかで使う
|
document.cookie = `token=${token}; path=/; max-age=31536000${ location.protocol === 'https:' ? '; Secure' : ''}`; // bull dashboardの認証とかで使う
|
||||||
await addAccount(me.id, token);
|
await addAccount(me.id, token);
|
||||||
|
|
||||||
if (redirect) {
|
if (redirect) {
|
||||||
|
|
|
@ -72,12 +72,16 @@ watch(() => props.lang, (to) => {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style module lang="scss">
|
<style module lang="scss">
|
||||||
|
.codeBlockRoot {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
.codeBlockRoot :global(.shiki) > code {
|
.codeBlockRoot :global(.shiki) > code {
|
||||||
counter-reset: step;
|
counter-reset: step;
|
||||||
counter-increment: step 0;
|
counter-increment: step 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.codeBlockRoot :global(.shiki) > code > .line::before {
|
.codeBlockRoot :global(.shiki) > code > span::before {
|
||||||
content: counter(step);
|
content: counter(step);
|
||||||
counter-increment: step;
|
counter-increment: step;
|
||||||
width: 1rem;
|
width: 1rem;
|
||||||
|
|
|
@ -40,7 +40,7 @@ const isScrolling = ref(false);
|
||||||
const scrollEl = shallowRef<HTMLElement>();
|
const scrollEl = shallowRef<HTMLElement>();
|
||||||
|
|
||||||
misskeyApiGet('notes/featured').then(_notes => {
|
misskeyApiGet('notes/featured').then(_notes => {
|
||||||
notes.value = _notes;
|
notes.value = _notes.filter(n => n.cw == null);
|
||||||
});
|
});
|
||||||
|
|
||||||
onUpdated(() => {
|
onUpdated(() => {
|
||||||
|
|
Loading…
Reference in a new issue