ストリーム経由でAPIにリクエストできるように
This commit is contained in:
		
							parent
							
								
									91791834be
								
							
						
					
					
						commit
						bd3d57a67f
					
				
					 15 changed files with 137 additions and 179 deletions
				
			
		| 
						 | 
					@ -444,23 +444,28 @@ export default class MiOS extends EventEmitter {
 | 
				
			||||||
		// Append a credential
 | 
							// Append a credential
 | 
				
			||||||
		if (this.isSignedIn) (data as any).i = this.i.token;
 | 
							if (this.isSignedIn) (data as any).i = this.i.token;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// TODO
 | 
							const viaStream = localStorage.getItem('enableExperimental') == 'true';
 | 
				
			||||||
		//const viaStream = localStorage.getItem('enableExperimental') == 'true';
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
		return new Promise((resolve, reject) => {
 | 
							return new Promise((resolve, reject) => {
 | 
				
			||||||
			/*if (viaStream) {
 | 
								if (viaStream) {
 | 
				
			||||||
				const stream = this.stream.borrow();
 | 
									const stream = this.stream.borrow();
 | 
				
			||||||
				const id = Math.random().toString();
 | 
									const id = Math.random().toString();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				stream.once(`api-res:${id}`, res => {
 | 
									stream.once(`api-res:${id}`, res => {
 | 
				
			||||||
					resolve(res);
 | 
										if (res.res) {
 | 
				
			||||||
 | 
											resolve(res.res);
 | 
				
			||||||
 | 
										} else {
 | 
				
			||||||
 | 
											reject(res.e);
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
				});
 | 
									});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				stream.send({
 | 
									stream.send({
 | 
				
			||||||
					type: 'api',
 | 
										type: 'api',
 | 
				
			||||||
					id,
 | 
										id,
 | 
				
			||||||
					endpoint,
 | 
										endpoint,
 | 
				
			||||||
					data
 | 
										data
 | 
				
			||||||
				});
 | 
									});
 | 
				
			||||||
			} else {*/
 | 
								} else {
 | 
				
			||||||
				const req = {
 | 
									const req = {
 | 
				
			||||||
					id: uuid(),
 | 
										id: uuid(),
 | 
				
			||||||
					date: new Date(),
 | 
										date: new Date(),
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -19,7 +19,7 @@ export type IApp = {
 | 
				
			||||||
	nameId: string;
 | 
						nameId: string;
 | 
				
			||||||
	nameIdLower: string;
 | 
						nameIdLower: string;
 | 
				
			||||||
	description: string;
 | 
						description: string;
 | 
				
			||||||
	permission: string;
 | 
						permission: string[];
 | 
				
			||||||
	callbackUrl: string;
 | 
						callbackUrl: string;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2,55 +2,33 @@ import * as express from 'express';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { Endpoint } from './endpoints';
 | 
					import { Endpoint } from './endpoints';
 | 
				
			||||||
import authenticate from './authenticate';
 | 
					import authenticate from './authenticate';
 | 
				
			||||||
import { IAuthContext } from './authenticate';
 | 
					import call from './call';
 | 
				
			||||||
import _reply from './reply';
 | 
					import { IUser } from '../../models/user';
 | 
				
			||||||
import limitter from './limitter';
 | 
					import { IApp } from '../../models/app';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default async (endpoint: Endpoint, req: express.Request, res: express.Response) => {
 | 
					export default async (endpoint: Endpoint, req: express.Request, res: express.Response) => {
 | 
				
			||||||
	const reply = _reply.bind(null, res);
 | 
						const reply = (x?: any, y?: any) => {
 | 
				
			||||||
	let ctx: IAuthContext;
 | 
							if (x === undefined) {
 | 
				
			||||||
 | 
								res.sendStatus(204);
 | 
				
			||||||
 | 
							} else if (typeof x === 'number') {
 | 
				
			||||||
 | 
								res.status(x).send({
 | 
				
			||||||
 | 
									error: x === 500 ? 'INTERNAL_ERROR' : y
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								res.send(x);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						let user: IUser;
 | 
				
			||||||
 | 
						let app: IApp;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Authentication
 | 
						// Authentication
 | 
				
			||||||
	try {
 | 
						try {
 | 
				
			||||||
		ctx = await authenticate(req);
 | 
							[user, app] = await authenticate(req.body['i']);
 | 
				
			||||||
	} catch (e) {
 | 
						} catch (e) {
 | 
				
			||||||
		return reply(403, 'AUTHENTICATION_FAILED');
 | 
							return reply(403, 'AUTHENTICATION_FAILED');
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (endpoint.secure && !ctx.isSecure) {
 | 
					 | 
				
			||||||
		return reply(403, 'ACCESS_DENIED');
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if (endpoint.withCredential && ctx.user == null) {
 | 
					 | 
				
			||||||
		return reply(401, 'PLZ_SIGNIN');
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if (ctx.app && endpoint.kind) {
 | 
					 | 
				
			||||||
		if (!ctx.app.permission.some(p => p === endpoint.kind)) {
 | 
					 | 
				
			||||||
			return reply(403, 'ACCESS_DENIED');
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if (endpoint.withCredential && endpoint.limit) {
 | 
					 | 
				
			||||||
		try {
 | 
					 | 
				
			||||||
			await limitter(endpoint, ctx); // Rate limit
 | 
					 | 
				
			||||||
		} catch (e) {
 | 
					 | 
				
			||||||
			// drop request if limit exceeded
 | 
					 | 
				
			||||||
			return reply(429);
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	let exec = require(`${__dirname}/endpoints/${endpoint.name}`);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if (endpoint.withFile) {
 | 
					 | 
				
			||||||
		exec = exec.bind(null, req.file);
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// API invoking
 | 
						// API invoking
 | 
				
			||||||
	try {
 | 
						call(endpoint, user, app, req.body, req).then(reply).catch(e => reply(400, e));
 | 
				
			||||||
		const res = await exec(req.body, ctx.user, ctx.app, ctx.isSecure);
 | 
					 | 
				
			||||||
		reply(res);
 | 
					 | 
				
			||||||
	} catch (e) {
 | 
					 | 
				
			||||||
		reply(400, e);
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,50 +1,24 @@
 | 
				
			||||||
import * as express from 'express';
 | 
					import App, { IApp } from '../../models/app';
 | 
				
			||||||
import App from '../../models/app';
 | 
					 | 
				
			||||||
import { default as User, IUser } from '../../models/user';
 | 
					import { default as User, IUser } from '../../models/user';
 | 
				
			||||||
import AccessToken from '../../models/access-token';
 | 
					import AccessToken from '../../models/access-token';
 | 
				
			||||||
import isNativeToken from './common/is-native-token';
 | 
					import isNativeToken from './common/is-native-token';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface IAuthContext {
 | 
					export default (token: string) => new Promise<[IUser, IApp]>(async (resolve, reject) => {
 | 
				
			||||||
	/**
 | 
					 | 
				
			||||||
	 * App which requested
 | 
					 | 
				
			||||||
	 */
 | 
					 | 
				
			||||||
	app: any;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	/**
 | 
					 | 
				
			||||||
	 * Authenticated user
 | 
					 | 
				
			||||||
	 */
 | 
					 | 
				
			||||||
	user: IUser;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	/**
 | 
					 | 
				
			||||||
	 * Whether requested with a User-Native Token
 | 
					 | 
				
			||||||
	 */
 | 
					 | 
				
			||||||
	isSecure: boolean;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default (req: express.Request) => new Promise<IAuthContext>(async (resolve, reject) => {
 | 
					 | 
				
			||||||
	const token = req.body['i'] as string;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if (token == null) {
 | 
						if (token == null) {
 | 
				
			||||||
		return resolve({
 | 
							resolve([null, null]);
 | 
				
			||||||
			app: null,
 | 
							return;
 | 
				
			||||||
			user: null,
 | 
					 | 
				
			||||||
			isSecure: false
 | 
					 | 
				
			||||||
		});
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (isNativeToken(token)) {
 | 
						if (isNativeToken(token)) {
 | 
				
			||||||
 | 
							// Fetch user
 | 
				
			||||||
		const user: IUser = await User
 | 
							const user: IUser = await User
 | 
				
			||||||
			.findOne({ 'token': token });
 | 
								.findOne({ token });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if (user === null) {
 | 
							if (user === null) {
 | 
				
			||||||
			return reject('user not found');
 | 
								return reject('user not found');
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		return resolve({
 | 
							resolve([user, null]);
 | 
				
			||||||
			app: null,
 | 
					 | 
				
			||||||
			user: user,
 | 
					 | 
				
			||||||
			isSecure: true
 | 
					 | 
				
			||||||
		});
 | 
					 | 
				
			||||||
	} else {
 | 
						} else {
 | 
				
			||||||
		const accessToken = await AccessToken.findOne({
 | 
							const accessToken = await AccessToken.findOne({
 | 
				
			||||||
			hash: token.toLowerCase()
 | 
								hash: token.toLowerCase()
 | 
				
			||||||
| 
						 | 
					@ -60,10 +34,6 @@ export default (req: express.Request) => new Promise<IAuthContext>(async (resolv
 | 
				
			||||||
		const user = await User
 | 
							const user = await User
 | 
				
			||||||
			.findOne({ _id: accessToken.userId });
 | 
								.findOne({ _id: accessToken.userId });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		return resolve({
 | 
							resolve([user, app]);
 | 
				
			||||||
			app: app,
 | 
					 | 
				
			||||||
			user: user,
 | 
					 | 
				
			||||||
			isSecure: false
 | 
					 | 
				
			||||||
		});
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										55
									
								
								src/server/api/call.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								src/server/api/call.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,55 @@
 | 
				
			||||||
 | 
					import * as express from 'express';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import endpoints, { Endpoint } from './endpoints';
 | 
				
			||||||
 | 
					import limitter from './limitter';
 | 
				
			||||||
 | 
					import { IUser } from '../../models/user';
 | 
				
			||||||
 | 
					import { IApp } from '../../models/app';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default (endpoint: string | Endpoint, user: IUser, app: IApp, data: any, req?: express.Request) => new Promise(async (ok, rej) => {
 | 
				
			||||||
 | 
						const isSecure = user != null && app == null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						//console.log(endpoint, user, app, data);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const ep = typeof endpoint == 'string' ? endpoints.find(e => e.name == endpoint) : endpoint;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (ep.secure && !isSecure) {
 | 
				
			||||||
 | 
							return rej('ACCESS_DENIED');
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (ep.withCredential && user == null) {
 | 
				
			||||||
 | 
							return rej('SIGNIN_REQUIRED');
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (app && ep.kind) {
 | 
				
			||||||
 | 
							if (!app.permission.some(p => p === ep.kind)) {
 | 
				
			||||||
 | 
								return rej('PERMISSION_DENIED');
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (ep.withCredential && ep.limit) {
 | 
				
			||||||
 | 
							try {
 | 
				
			||||||
 | 
								await limitter(ep, user); // Rate limit
 | 
				
			||||||
 | 
							} catch (e) {
 | 
				
			||||||
 | 
								// drop request if limit exceeded
 | 
				
			||||||
 | 
								return rej('RATE_LIMIT_EXCEEDED');
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						let exec = require(`${__dirname}/endpoints/${ep.name}`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (ep.withFile && req) {
 | 
				
			||||||
 | 
							exec = exec.bind(null, req.file);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						let res;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// API invoking
 | 
				
			||||||
 | 
						try {
 | 
				
			||||||
 | 
							res = await exec(data, user, app);
 | 
				
			||||||
 | 
						} catch (e) {
 | 
				
			||||||
 | 
							rej(e);
 | 
				
			||||||
 | 
							return;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ok(res);
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
| 
						 | 
					@ -36,14 +36,10 @@ import App, { pack } from '../../../../models/app';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Show an app
 | 
					 * Show an app
 | 
				
			||||||
 *
 | 
					 | 
				
			||||||
 * @param {any} params
 | 
					 | 
				
			||||||
 * @param {any} user
 | 
					 | 
				
			||||||
 * @param {any} _
 | 
					 | 
				
			||||||
 * @param {any} isSecure
 | 
					 | 
				
			||||||
 * @return {Promise<any>}
 | 
					 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
module.exports = (params, user, _, isSecure) => new Promise(async (res, rej) => {
 | 
					module.exports = (params, user, app) => new Promise(async (res, rej) => {
 | 
				
			||||||
 | 
						const isSecure = user != null && app == null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Get 'appId' parameter
 | 
						// Get 'appId' parameter
 | 
				
			||||||
	const [appId, appIdErr] = $(params.appId).optional.id().$;
 | 
						const [appId, appIdErr] = $(params.appId).optional.id().$;
 | 
				
			||||||
	if (appIdErr) return rej('invalid appId param');
 | 
						if (appIdErr) return rej('invalid appId param');
 | 
				
			||||||
| 
						 | 
					@ -57,16 +53,16 @@ module.exports = (params, user, _, isSecure) => new Promise(async (res, rej) =>
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Lookup app
 | 
						// Lookup app
 | 
				
			||||||
	const app = appId !== undefined
 | 
						const ap = appId !== undefined
 | 
				
			||||||
		? await App.findOne({ _id: appId })
 | 
							? await App.findOne({ _id: appId })
 | 
				
			||||||
		: await App.findOne({ nameIdLower: nameId.toLowerCase() });
 | 
							: await App.findOne({ nameIdLower: nameId.toLowerCase() });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (app === null) {
 | 
						if (ap === null) {
 | 
				
			||||||
		return rej('app not found');
 | 
							return rej('app not found');
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Send response
 | 
						// Send response
 | 
				
			||||||
	res(await pack(app, user, {
 | 
						res(await pack(ap, user, {
 | 
				
			||||||
		includeSecret: isSecure && app.userId.equals(user._id)
 | 
							includeSecret: isSecure && ap.userId.equals(user._id)
 | 
				
			||||||
	}));
 | 
						}));
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -6,7 +6,9 @@ import User, { pack } from '../../../models/user';
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Show myself
 | 
					 * Show myself
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
module.exports = (params, user, _, isSecure) => new Promise(async (res, rej) => {
 | 
					module.exports = (params, user, app) => new Promise(async (res, rej) => {
 | 
				
			||||||
 | 
						const isSecure = user != null && app == null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Serialize
 | 
						// Serialize
 | 
				
			||||||
	res(await pack(user, user, {
 | 
						res(await pack(user, user, {
 | 
				
			||||||
		detail: true,
 | 
							detail: true,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -7,14 +7,10 @@ import event from '../../../../publishers/stream';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Update myself
 | 
					 * Update myself
 | 
				
			||||||
 *
 | 
					 | 
				
			||||||
 * @param {any} params
 | 
					 | 
				
			||||||
 * @param {any} user
 | 
					 | 
				
			||||||
 * @param {any} _
 | 
					 | 
				
			||||||
 * @param {boolean} isSecure
 | 
					 | 
				
			||||||
 * @return {Promise<any>}
 | 
					 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
module.exports = async (params, user, _, isSecure) => new Promise(async (res, rej) => {
 | 
					module.exports = async (params, user, app) => new Promise(async (res, rej) => {
 | 
				
			||||||
 | 
						const isSecure = user != null && app == null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Get 'name' parameter
 | 
						// Get 'name' parameter
 | 
				
			||||||
	const [name, nameErr] = $(params.name).optional.nullable.string().pipe(isValidName).$;
 | 
						const [name, nameErr] = $(params.name).optional.nullable.string().pipe(isValidName).$;
 | 
				
			||||||
	if (nameErr) return rej('invalid name param');
 | 
						if (nameErr) return rej('invalid name param');
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -35,9 +35,6 @@ import Meta from '../../../models/meta';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Show core info
 | 
					 * Show core info
 | 
				
			||||||
 *
 | 
					 | 
				
			||||||
 * @param {any} params
 | 
					 | 
				
			||||||
 * @return {Promise<any>}
 | 
					 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
module.exports = (params) => new Promise(async (res, rej) => {
 | 
					module.exports = (params) => new Promise(async (res, rej) => {
 | 
				
			||||||
	const meta: any = (await Meta.findOne()) || {};
 | 
						const meta: any = (await Meta.findOne()) || {};
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -6,14 +6,8 @@ import Subscription from '../../../../models/sw-subscription';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * subscribe service worker
 | 
					 * subscribe service worker
 | 
				
			||||||
 *
 | 
					 | 
				
			||||||
 * @param {any} params
 | 
					 | 
				
			||||||
 * @param {any} user
 | 
					 | 
				
			||||||
 * @param {any} _
 | 
					 | 
				
			||||||
 * @param {boolean} isSecure
 | 
					 | 
				
			||||||
 * @return {Promise<any>}
 | 
					 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
module.exports = async (params, user, _, isSecure) => new Promise(async (res, rej) => {
 | 
					module.exports = async (params, user, app) => new Promise(async (res, rej) => {
 | 
				
			||||||
	// Get 'endpoint' parameter
 | 
						// Get 'endpoint' parameter
 | 
				
			||||||
	const [endpoint, endpointErr] = $(params.endpoint).string().$;
 | 
						const [endpoint, endpointErr] = $(params.endpoint).string().$;
 | 
				
			||||||
	if (endpointErr) return rej('invalid endpoint param');
 | 
						if (endpointErr) return rej('invalid endpoint param');
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -7,7 +7,6 @@ import * as bodyParser from 'body-parser';
 | 
				
			||||||
import * as cors from 'cors';
 | 
					import * as cors from 'cors';
 | 
				
			||||||
import * as multer from 'multer';
 | 
					import * as multer from 'multer';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// import authenticate from './authenticate';
 | 
					 | 
				
			||||||
import endpoints from './endpoints';
 | 
					import endpoints from './endpoints';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2,12 +2,12 @@ import * as Limiter from 'ratelimiter';
 | 
				
			||||||
import * as debug from 'debug';
 | 
					import * as debug from 'debug';
 | 
				
			||||||
import limiterDB from '../../db/redis';
 | 
					import limiterDB from '../../db/redis';
 | 
				
			||||||
import { Endpoint } from './endpoints';
 | 
					import { Endpoint } from './endpoints';
 | 
				
			||||||
import { IAuthContext } from './authenticate';
 | 
					 | 
				
			||||||
import getAcct from '../../acct/render';
 | 
					import getAcct from '../../acct/render';
 | 
				
			||||||
 | 
					import { IUser } from '../../models/user';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const log = debug('misskey:limitter');
 | 
					const log = debug('misskey:limitter');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default (endpoint: Endpoint, ctx: IAuthContext) => new Promise((ok, reject) => {
 | 
					export default (endpoint: Endpoint, user: IUser) => new Promise((ok, reject) => {
 | 
				
			||||||
	const limitation = endpoint.limit;
 | 
						const limitation = endpoint.limit;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	const key = limitation.hasOwnProperty('key')
 | 
						const key = limitation.hasOwnProperty('key')
 | 
				
			||||||
| 
						 | 
					@ -32,7 +32,7 @@ export default (endpoint: Endpoint, ctx: IAuthContext) => new Promise((ok, rejec
 | 
				
			||||||
	// Short-term limit
 | 
						// Short-term limit
 | 
				
			||||||
	function min() {
 | 
						function min() {
 | 
				
			||||||
		const minIntervalLimiter = new Limiter({
 | 
							const minIntervalLimiter = new Limiter({
 | 
				
			||||||
			id: `${ctx.user._id}:${key}:min`,
 | 
								id: `${user._id}:${key}:min`,
 | 
				
			||||||
			duration: limitation.minInterval,
 | 
								duration: limitation.minInterval,
 | 
				
			||||||
			max: 1,
 | 
								max: 1,
 | 
				
			||||||
			db: limiterDB
 | 
								db: limiterDB
 | 
				
			||||||
| 
						 | 
					@ -43,7 +43,7 @@ export default (endpoint: Endpoint, ctx: IAuthContext) => new Promise((ok, rejec
 | 
				
			||||||
				return reject('ERR');
 | 
									return reject('ERR');
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			log(`@${getAcct(ctx.user)} ${endpoint.name} min remaining: ${info.remaining}`);
 | 
								log(`@${getAcct(user)} ${endpoint.name} min remaining: ${info.remaining}`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			if (info.remaining === 0) {
 | 
								if (info.remaining === 0) {
 | 
				
			||||||
				reject('BRIEF_REQUEST_INTERVAL');
 | 
									reject('BRIEF_REQUEST_INTERVAL');
 | 
				
			||||||
| 
						 | 
					@ -60,7 +60,7 @@ export default (endpoint: Endpoint, ctx: IAuthContext) => new Promise((ok, rejec
 | 
				
			||||||
	// Long term limit
 | 
						// Long term limit
 | 
				
			||||||
	function max() {
 | 
						function max() {
 | 
				
			||||||
		const limiter = new Limiter({
 | 
							const limiter = new Limiter({
 | 
				
			||||||
			id: `${ctx.user._id}:${key}`,
 | 
								id: `${user._id}:${key}`,
 | 
				
			||||||
			duration: limitation.duration,
 | 
								duration: limitation.duration,
 | 
				
			||||||
			max: limitation.max,
 | 
								max: limitation.max,
 | 
				
			||||||
			db: limiterDB
 | 
								db: limiterDB
 | 
				
			||||||
| 
						 | 
					@ -71,7 +71,7 @@ export default (endpoint: Endpoint, ctx: IAuthContext) => new Promise((ok, rejec
 | 
				
			||||||
				return reject('ERR');
 | 
									return reject('ERR');
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			log(`@${getAcct(ctx.user)} ${endpoint.name} max remaining: ${info.remaining}`);
 | 
								log(`@${getAcct(user)} ${endpoint.name} max remaining: ${info.remaining}`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			if (info.remaining === 0) {
 | 
								if (info.remaining === 0) {
 | 
				
			||||||
				reject('RATE_LIMIT_EXCEEDED');
 | 
									reject('RATE_LIMIT_EXCEEDED');
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,13 +0,0 @@
 | 
				
			||||||
import * as express from 'express';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default (res: express.Response, x?: any, y?: any) => {
 | 
					 | 
				
			||||||
	if (x === undefined) {
 | 
					 | 
				
			||||||
		res.sendStatus(204);
 | 
					 | 
				
			||||||
	} else if (typeof x === 'number') {
 | 
					 | 
				
			||||||
		res.status(x).send({
 | 
					 | 
				
			||||||
			error: x === 500 ? 'INTERNAL_ERROR' : y
 | 
					 | 
				
			||||||
		});
 | 
					 | 
				
			||||||
	} else {
 | 
					 | 
				
			||||||
		res.send(x);
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
| 
						 | 
					@ -2,14 +2,22 @@ import * as websocket from 'websocket';
 | 
				
			||||||
import * as redis from 'redis';
 | 
					import * as redis from 'redis';
 | 
				
			||||||
import * as debug from 'debug';
 | 
					import * as debug from 'debug';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import User from '../../../models/user';
 | 
					import User, { IUser } from '../../../models/user';
 | 
				
			||||||
import Mute from '../../../models/mute';
 | 
					import Mute from '../../../models/mute';
 | 
				
			||||||
import { pack as packNote } from '../../../models/note';
 | 
					import { pack as packNote } from '../../../models/note';
 | 
				
			||||||
import readNotification from '../common/read-notification';
 | 
					import readNotification from '../common/read-notification';
 | 
				
			||||||
 | 
					import call from '../call';
 | 
				
			||||||
 | 
					import { IApp } from '../../../models/app';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const log = debug('misskey');
 | 
					const log = debug('misskey');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default async function(request: websocket.request, connection: websocket.connection, subscriber: redis.RedisClient, user: any) {
 | 
					export default async function(
 | 
				
			||||||
 | 
						request: websocket.request,
 | 
				
			||||||
 | 
						connection: websocket.connection,
 | 
				
			||||||
 | 
						subscriber: redis.RedisClient,
 | 
				
			||||||
 | 
						user: IUser,
 | 
				
			||||||
 | 
						app: IApp
 | 
				
			||||||
 | 
					) {
 | 
				
			||||||
	// Subscribe Home stream channel
 | 
						// Subscribe Home stream channel
 | 
				
			||||||
	subscriber.subscribe(`misskey:user-stream:${user._id}`);
 | 
						subscriber.subscribe(`misskey:user-stream:${user._id}`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -67,7 +75,17 @@ export default async function(request: websocket.request, connection: websocket.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		switch (msg.type) {
 | 
							switch (msg.type) {
 | 
				
			||||||
			case 'api':
 | 
								case 'api':
 | 
				
			||||||
				// TODO
 | 
									call(msg.endpoint, user, app, msg.data).then(res => {
 | 
				
			||||||
 | 
										connection.send(JSON.stringify({
 | 
				
			||||||
 | 
											type: `api-res:${msg.id}`,
 | 
				
			||||||
 | 
											body: { res }
 | 
				
			||||||
 | 
										}));
 | 
				
			||||||
 | 
									}).catch(e => {
 | 
				
			||||||
 | 
										connection.send(JSON.stringify({
 | 
				
			||||||
 | 
											type: `api-res:${msg.id}`,
 | 
				
			||||||
 | 
											body: { e }
 | 
				
			||||||
 | 
										}));
 | 
				
			||||||
 | 
									});
 | 
				
			||||||
				break;
 | 
									break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			case 'alive':
 | 
								case 'alive':
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2,9 +2,6 @@ import * as http from 'http';
 | 
				
			||||||
import * as websocket from 'websocket';
 | 
					import * as websocket from 'websocket';
 | 
				
			||||||
import * as redis from 'redis';
 | 
					import * as redis from 'redis';
 | 
				
			||||||
import config from '../../config';
 | 
					import config from '../../config';
 | 
				
			||||||
import { default as User, IUser } from '../../models/user';
 | 
					 | 
				
			||||||
import AccessToken from '../../models/access-token';
 | 
					 | 
				
			||||||
import isNativeToken from './common/is-native-token';
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
import homeStream from './stream/home';
 | 
					import homeStream from './stream/home';
 | 
				
			||||||
import driveStream from './stream/drive';
 | 
					import driveStream from './stream/drive';
 | 
				
			||||||
| 
						 | 
					@ -16,6 +13,7 @@ import serverStream from './stream/server';
 | 
				
			||||||
import requestsStream from './stream/requests';
 | 
					import requestsStream from './stream/requests';
 | 
				
			||||||
import channelStream from './stream/channel';
 | 
					import channelStream from './stream/channel';
 | 
				
			||||||
import { ParsedUrlQuery } from 'querystring';
 | 
					import { ParsedUrlQuery } from 'querystring';
 | 
				
			||||||
 | 
					import authenticate from './authenticate';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
module.exports = (server: http.Server) => {
 | 
					module.exports = (server: http.Server) => {
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
| 
						 | 
					@ -53,7 +51,7 @@ module.exports = (server: http.Server) => {
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		const q = request.resourceURL.query as ParsedUrlQuery;
 | 
							const q = request.resourceURL.query as ParsedUrlQuery;
 | 
				
			||||||
		const user = await authenticate(q.i as string);
 | 
							const [user, app] = await authenticate(q.i as string);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if (request.resourceURL.pathname === '/othello-game') {
 | 
							if (request.resourceURL.pathname === '/othello-game') {
 | 
				
			||||||
			othelloGameStream(request, connection, subscriber, user);
 | 
								othelloGameStream(request, connection, subscriber, user);
 | 
				
			||||||
| 
						 | 
					@ -75,46 +73,9 @@ module.exports = (server: http.Server) => {
 | 
				
			||||||
			null;
 | 
								null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if (channel !== null) {
 | 
							if (channel !== null) {
 | 
				
			||||||
			channel(request, connection, subscriber, user);
 | 
								channel(request, connection, subscriber, user, app);
 | 
				
			||||||
		} else {
 | 
							} else {
 | 
				
			||||||
			connection.close();
 | 
								connection.close();
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	});
 | 
						});
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * 接続してきたユーザーを取得します
 | 
					 | 
				
			||||||
 * @param token 送信されてきたトークン
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
function authenticate(token: string): Promise<IUser> {
 | 
					 | 
				
			||||||
	if (token == null) {
 | 
					 | 
				
			||||||
		return Promise.resolve(null);
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return new Promise(async (resolve, reject) => {
 | 
					 | 
				
			||||||
		if (isNativeToken(token)) {
 | 
					 | 
				
			||||||
			// Fetch user
 | 
					 | 
				
			||||||
			const user: IUser = await User
 | 
					 | 
				
			||||||
				.findOne({
 | 
					 | 
				
			||||||
					host: null,
 | 
					 | 
				
			||||||
					'token': token
 | 
					 | 
				
			||||||
				});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			resolve(user);
 | 
					 | 
				
			||||||
		} else {
 | 
					 | 
				
			||||||
			const accessToken = await AccessToken.findOne({
 | 
					 | 
				
			||||||
				hash: token
 | 
					 | 
				
			||||||
			});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			if (accessToken == null) {
 | 
					 | 
				
			||||||
				return reject('invalid signature');
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			// Fetch user
 | 
					 | 
				
			||||||
			const user: IUser = await User
 | 
					 | 
				
			||||||
				.findOne({ _id: accessToken.userId });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			resolve(user);
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	});
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue