This commit is contained in:
		
							parent
							
								
									d680b6bd9b
								
							
						
					
					
						commit
						767a292dbd
					
				
					 6 changed files with 128 additions and 55 deletions
				
			
		
							
								
								
									
										70
									
								
								package.json
									
										
									
									
									
								
							
							
						
						
									
										70
									
								
								package.json
									
										
									
									
									
								
							| 
						 | 
					@ -22,11 +22,14 @@
 | 
				
			||||||
		"format": "gulp format"
 | 
							"format": "gulp format"
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
	"dependencies": {
 | 
						"dependencies": {
 | 
				
			||||||
 | 
							"@prezzemolo/rap": "0.1.2",
 | 
				
			||||||
 | 
							"@prezzemolo/zip": "0.0.3",
 | 
				
			||||||
		"@types/bcryptjs": "2.4.1",
 | 
							"@types/bcryptjs": "2.4.1",
 | 
				
			||||||
		"@types/body-parser": "1.16.7",
 | 
							"@types/body-parser": "1.16.7",
 | 
				
			||||||
		"@types/chai": "4.0.4",
 | 
							"@types/chai": "4.0.4",
 | 
				
			||||||
		"@types/chai-http": "3.0.3",
 | 
							"@types/chai-http": "3.0.3",
 | 
				
			||||||
		"@types/compression": "0.0.34",
 | 
							"@types/compression": "0.0.34",
 | 
				
			||||||
 | 
							"@types/cookie": "^0.3.1",
 | 
				
			||||||
		"@types/cors": "2.8.1",
 | 
							"@types/cors": "2.8.1",
 | 
				
			||||||
		"@types/debug": "0.0.30",
 | 
							"@types/debug": "0.0.30",
 | 
				
			||||||
		"@types/deep-equal": "1.0.1",
 | 
							"@types/deep-equal": "1.0.1",
 | 
				
			||||||
| 
						 | 
					@ -67,11 +70,33 @@
 | 
				
			||||||
		"@types/webpack": "3.8.0",
 | 
							"@types/webpack": "3.8.0",
 | 
				
			||||||
		"@types/webpack-stream": "3.2.8",
 | 
							"@types/webpack-stream": "3.2.8",
 | 
				
			||||||
		"@types/websocket": "0.0.34",
 | 
							"@types/websocket": "0.0.34",
 | 
				
			||||||
 | 
							"accesses": "2.5.0",
 | 
				
			||||||
 | 
							"animejs": "2.2.0",
 | 
				
			||||||
 | 
							"autwh": "0.0.1",
 | 
				
			||||||
		"awesome-typescript-loader": "3.4.0",
 | 
							"awesome-typescript-loader": "3.4.0",
 | 
				
			||||||
 | 
							"bcryptjs": "2.4.3",
 | 
				
			||||||
 | 
							"body-parser": "1.18.2",
 | 
				
			||||||
 | 
							"cafy": "3.2.0",
 | 
				
			||||||
		"chai": "4.1.2",
 | 
							"chai": "4.1.2",
 | 
				
			||||||
		"chai-http": "3.0.0",
 | 
							"chai-http": "3.0.0",
 | 
				
			||||||
 | 
							"chalk": "2.3.0",
 | 
				
			||||||
 | 
							"compression": "1.7.1",
 | 
				
			||||||
 | 
							"cookie": "^0.3.1",
 | 
				
			||||||
 | 
							"cors": "2.8.4",
 | 
				
			||||||
 | 
							"cropperjs": "1.1.3",
 | 
				
			||||||
		"css-loader": "0.28.7",
 | 
							"css-loader": "0.28.7",
 | 
				
			||||||
 | 
							"debug": "3.1.0",
 | 
				
			||||||
 | 
							"deep-equal": "1.0.1",
 | 
				
			||||||
 | 
							"deepcopy": "0.6.3",
 | 
				
			||||||
 | 
							"diskusage": "0.2.2",
 | 
				
			||||||
 | 
							"elasticsearch": "13.3.1",
 | 
				
			||||||
 | 
							"escape-regexp": "0.0.1",
 | 
				
			||||||
		"event-stream": "3.3.4",
 | 
							"event-stream": "3.3.4",
 | 
				
			||||||
 | 
							"eventemitter3": "^2.0.3",
 | 
				
			||||||
 | 
							"express": "4.15.4",
 | 
				
			||||||
 | 
							"file-type": "7.2.0",
 | 
				
			||||||
 | 
							"fuckadblock": "3.2.1",
 | 
				
			||||||
 | 
							"gm": "1.23.0",
 | 
				
			||||||
		"gulp": "3.9.1",
 | 
							"gulp": "3.9.1",
 | 
				
			||||||
		"gulp-cssnano": "2.1.2",
 | 
							"gulp-cssnano": "2.1.2",
 | 
				
			||||||
		"gulp-htmlmin": "3.0.0",
 | 
							"gulp-htmlmin": "3.0.0",
 | 
				
			||||||
| 
						 | 
					@ -84,45 +109,12 @@
 | 
				
			||||||
		"gulp-typescript": "3.2.3",
 | 
							"gulp-typescript": "3.2.3",
 | 
				
			||||||
		"gulp-uglify": "3.0.0",
 | 
							"gulp-uglify": "3.0.0",
 | 
				
			||||||
		"gulp-util": "3.0.8",
 | 
							"gulp-util": "3.0.8",
 | 
				
			||||||
		"mocha": "4.0.1",
 | 
					 | 
				
			||||||
		"riot-tag-loader": "1.0.0",
 | 
					 | 
				
			||||||
		"string-replace-webpack-plugin": "0.1.3",
 | 
					 | 
				
			||||||
		"style-loader": "0.19.0",
 | 
					 | 
				
			||||||
		"stylus": "0.54.5",
 | 
					 | 
				
			||||||
		"stylus-loader": "3.0.1",
 | 
					 | 
				
			||||||
		"swagger-jsdoc": "1.9.7",
 | 
					 | 
				
			||||||
		"tslint": "5.8.0",
 | 
					 | 
				
			||||||
		"uglify-es": "3.1.8",
 | 
					 | 
				
			||||||
		"uglifyjs-webpack-plugin": "1.1.0",
 | 
					 | 
				
			||||||
		"webpack": "3.8.1",
 | 
					 | 
				
			||||||
		"@prezzemolo/rap": "0.1.2",
 | 
					 | 
				
			||||||
		"@prezzemolo/zip": "0.0.3",
 | 
					 | 
				
			||||||
		"accesses": "2.5.0",
 | 
					 | 
				
			||||||
		"animejs": "2.2.0",
 | 
					 | 
				
			||||||
		"autwh": "0.0.1",
 | 
					 | 
				
			||||||
		"bcryptjs": "2.4.3",
 | 
					 | 
				
			||||||
		"body-parser": "1.18.2",
 | 
					 | 
				
			||||||
		"cafy": "3.2.0",
 | 
					 | 
				
			||||||
		"chalk": "2.3.0",
 | 
					 | 
				
			||||||
		"compression": "1.7.1",
 | 
					 | 
				
			||||||
		"cors": "2.8.4",
 | 
					 | 
				
			||||||
		"cropperjs": "1.1.3",
 | 
					 | 
				
			||||||
		"debug": "3.1.0",
 | 
					 | 
				
			||||||
		"deep-equal": "1.0.1",
 | 
					 | 
				
			||||||
		"deepcopy": "0.6.3",
 | 
					 | 
				
			||||||
		"diskusage": "0.2.2",
 | 
					 | 
				
			||||||
		"elasticsearch": "13.3.1",
 | 
					 | 
				
			||||||
		"escape-regexp": "0.0.1",
 | 
					 | 
				
			||||||
		"eventemitter3": "^2.0.3",
 | 
					 | 
				
			||||||
		"express": "4.15.4",
 | 
					 | 
				
			||||||
		"file-type": "7.2.0",
 | 
					 | 
				
			||||||
		"fuckadblock": "3.2.1",
 | 
					 | 
				
			||||||
		"gm": "1.23.0",
 | 
					 | 
				
			||||||
		"inquirer": "3.3.0",
 | 
							"inquirer": "3.3.0",
 | 
				
			||||||
		"is-root": "1.0.0",
 | 
							"is-root": "1.0.0",
 | 
				
			||||||
		"is-url": "1.2.2",
 | 
							"is-url": "1.2.2",
 | 
				
			||||||
		"js-yaml": "3.10.0",
 | 
							"js-yaml": "3.10.0",
 | 
				
			||||||
		"mecab-async": "^0.1.0",
 | 
							"mecab-async": "^0.1.0",
 | 
				
			||||||
 | 
							"mocha": "4.0.1",
 | 
				
			||||||
		"moji": "^0.5.1",
 | 
							"moji": "^0.5.1",
 | 
				
			||||||
		"mongodb": "2.2.33",
 | 
							"mongodb": "2.2.33",
 | 
				
			||||||
		"monk": "6.0.5",
 | 
							"monk": "6.0.5",
 | 
				
			||||||
| 
						 | 
					@ -143,21 +135,31 @@
 | 
				
			||||||
		"request": "^2.83.0",
 | 
							"request": "^2.83.0",
 | 
				
			||||||
		"rimraf": "2.6.2",
 | 
							"rimraf": "2.6.2",
 | 
				
			||||||
		"riot": "3.7.4",
 | 
							"riot": "3.7.4",
 | 
				
			||||||
 | 
							"riot-tag-loader": "1.0.0",
 | 
				
			||||||
		"rndstr": "1.0.0",
 | 
							"rndstr": "1.0.0",
 | 
				
			||||||
		"s-age": "1.1.0",
 | 
							"s-age": "1.1.0",
 | 
				
			||||||
		"seedrandom": "^2.4.3",
 | 
							"seedrandom": "^2.4.3",
 | 
				
			||||||
		"serve-favicon": "2.4.5",
 | 
							"serve-favicon": "2.4.5",
 | 
				
			||||||
		"sortablejs": "1.7.0",
 | 
							"sortablejs": "1.7.0",
 | 
				
			||||||
 | 
							"string-replace-webpack-plugin": "0.1.3",
 | 
				
			||||||
 | 
							"style-loader": "0.19.0",
 | 
				
			||||||
 | 
							"stylus": "0.54.5",
 | 
				
			||||||
 | 
							"stylus-loader": "3.0.1",
 | 
				
			||||||
		"summaly": "2.0.3",
 | 
							"summaly": "2.0.3",
 | 
				
			||||||
 | 
							"swagger-jsdoc": "1.9.7",
 | 
				
			||||||
		"syuilo-password-strength": "0.0.1",
 | 
							"syuilo-password-strength": "0.0.1",
 | 
				
			||||||
		"tcp-port-used": "0.1.2",
 | 
							"tcp-port-used": "0.1.2",
 | 
				
			||||||
		"textarea-caret": "3.0.2",
 | 
							"textarea-caret": "3.0.2",
 | 
				
			||||||
		"tmp": "0.0.33",
 | 
							"tmp": "0.0.33",
 | 
				
			||||||
		"ts-node": "3.3.0",
 | 
							"ts-node": "3.3.0",
 | 
				
			||||||
 | 
							"tslint": "5.8.0",
 | 
				
			||||||
		"typescript": "2.6.1",
 | 
							"typescript": "2.6.1",
 | 
				
			||||||
 | 
							"uglify-es": "3.1.8",
 | 
				
			||||||
 | 
							"uglifyjs-webpack-plugin": "1.1.0",
 | 
				
			||||||
		"uuid": "3.1.0",
 | 
							"uuid": "3.1.0",
 | 
				
			||||||
		"vhost": "3.0.2",
 | 
							"vhost": "3.0.2",
 | 
				
			||||||
		"web-push": "^3.2.4",
 | 
							"web-push": "^3.2.4",
 | 
				
			||||||
 | 
							"webpack": "3.8.1",
 | 
				
			||||||
		"websocket": "1.0.25",
 | 
							"websocket": "1.0.25",
 | 
				
			||||||
		"xev": "2.0.0"
 | 
							"xev": "2.0.0"
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										19
									
								
								src/api/common/signin.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								src/api/common/signin.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,19 @@
 | 
				
			||||||
 | 
					import config from '../../conf';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default function(res, user, redirect: boolean) {
 | 
				
			||||||
 | 
						const expires = 1000 * 60 * 60 * 24 * 365; // One Year
 | 
				
			||||||
 | 
						res.cookie('i', user.token, {
 | 
				
			||||||
 | 
							path: '/',
 | 
				
			||||||
 | 
							domain: `.${config.host}`,
 | 
				
			||||||
 | 
							secure: config.url.substr(0, 5) === 'https',
 | 
				
			||||||
 | 
							httpOnly: false,
 | 
				
			||||||
 | 
							expires: new Date(Date.now() + expires),
 | 
				
			||||||
 | 
							maxAge: expires
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (redirect) {
 | 
				
			||||||
 | 
							res.redirect(config.url);
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							res.sendStatus(204);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -4,7 +4,7 @@ import { default as User, IUser } from '../models/user';
 | 
				
			||||||
import Signin from '../models/signin';
 | 
					import Signin from '../models/signin';
 | 
				
			||||||
import serialize from '../serializers/signin';
 | 
					import serialize from '../serializers/signin';
 | 
				
			||||||
import event from '../event';
 | 
					import event from '../event';
 | 
				
			||||||
import config from '../../conf';
 | 
					import signin from '../common/signin';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default async (req: express.Request, res: express.Response) => {
 | 
					export default async (req: express.Request, res: express.Response) => {
 | 
				
			||||||
	res.header('Access-Control-Allow-Credentials', 'true');
 | 
						res.header('Access-Control-Allow-Credentials', 'true');
 | 
				
			||||||
| 
						 | 
					@ -43,17 +43,7 @@ export default async (req: express.Request, res: express.Response) => {
 | 
				
			||||||
	const same = await bcrypt.compare(password, user.password);
 | 
						const same = await bcrypt.compare(password, user.password);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (same) {
 | 
						if (same) {
 | 
				
			||||||
		const expires = 1000 * 60 * 60 * 24 * 365; // One Year
 | 
							signin(res, user, false);
 | 
				
			||||||
		res.cookie('i', user.token, {
 | 
					 | 
				
			||||||
			path: '/',
 | 
					 | 
				
			||||||
			domain: `.${config.host}`,
 | 
					 | 
				
			||||||
			secure: config.url.substr(0, 5) === 'https',
 | 
					 | 
				
			||||||
			httpOnly: false,
 | 
					 | 
				
			||||||
			expires: new Date(Date.now() + expires),
 | 
					 | 
				
			||||||
			maxAge: expires
 | 
					 | 
				
			||||||
		});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		res.sendStatus(204);
 | 
					 | 
				
			||||||
	} else {
 | 
						} else {
 | 
				
			||||||
		res.status(400).send({
 | 
							res.status(400).send({
 | 
				
			||||||
			error: 'incorrect password'
 | 
								error: 'incorrect password'
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,4 +1,6 @@
 | 
				
			||||||
import * as express from 'express';
 | 
					import * as express from 'express';
 | 
				
			||||||
 | 
					import * as cookie from 'cookie';
 | 
				
			||||||
 | 
					import * as uuid from 'uuid';
 | 
				
			||||||
// import * as Twitter from 'twitter';
 | 
					// import * as Twitter from 'twitter';
 | 
				
			||||||
// const Twitter = require('twitter');
 | 
					// const Twitter = require('twitter');
 | 
				
			||||||
import autwh from 'autwh';
 | 
					import autwh from 'autwh';
 | 
				
			||||||
| 
						 | 
					@ -7,6 +9,7 @@ import User from '../models/user';
 | 
				
			||||||
import serialize from '../serializers/user';
 | 
					import serialize from '../serializers/user';
 | 
				
			||||||
import event from '../event';
 | 
					import event from '../event';
 | 
				
			||||||
import config from '../../conf';
 | 
					import config from '../../conf';
 | 
				
			||||||
 | 
					import signin from '../common/signin';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
module.exports = (app: express.Application) => {
 | 
					module.exports = (app: express.Application) => {
 | 
				
			||||||
	app.get('/disconnect/twitter', async (req, res): Promise<any> => {
 | 
						app.get('/disconnect/twitter', async (req, res): Promise<any> => {
 | 
				
			||||||
| 
						 | 
					@ -30,8 +33,13 @@ module.exports = (app: express.Application) => {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (config.twitter == null) {
 | 
						if (config.twitter == null) {
 | 
				
			||||||
		app.get('/connect/twitter', (req, res) => {
 | 
							app.get('/connect/twitter', (req, res) => {
 | 
				
			||||||
			res.send('現在Twitterへ接続できません');
 | 
								res.send('現在Twitterへ接続できません (このインスタンスではTwitterはサポートされていません)');
 | 
				
			||||||
		});
 | 
							});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							app.get('/signin/twitter', (req, res) => {
 | 
				
			||||||
 | 
								res.send('現在Twitterへ接続できません (このインスタンスではTwitterはサポートされていません)');
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		return;
 | 
							return;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -41,6 +49,12 @@ module.exports = (app: express.Application) => {
 | 
				
			||||||
		callbackUrl: `${config.api_url}/tw/cb`
 | 
							callbackUrl: `${config.api_url}/tw/cb`
 | 
				
			||||||
	});
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const twAuthSignin = autwh({
 | 
				
			||||||
 | 
							consumerKey: config.twitter.consumer_key,
 | 
				
			||||||
 | 
							consumerSecret: config.twitter.consumer_secret,
 | 
				
			||||||
 | 
							callbackUrl: `${config.api_url}/signin/twitter/cb`
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	app.get('/connect/twitter', async (req, res): Promise<any> => {
 | 
						app.get('/connect/twitter', async (req, res): Promise<any> => {
 | 
				
			||||||
		if (res.locals.user == null) return res.send('plz signin');
 | 
							if (res.locals.user == null) return res.send('plz signin');
 | 
				
			||||||
		const ctx = await twAuth.begin();
 | 
							const ctx = await twAuth.begin();
 | 
				
			||||||
| 
						 | 
					@ -75,4 +89,50 @@ module.exports = (app: express.Application) => {
 | 
				
			||||||
			}));
 | 
								}));
 | 
				
			||||||
		});
 | 
							});
 | 
				
			||||||
	});
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						app.get('/signin/twitter', async (req, res): Promise<any> => {
 | 
				
			||||||
 | 
							const ctx = await twAuthSignin.begin();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							const sessid = uuid();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							redis.set(sessid, JSON.stringify(ctx));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							const expires = 1000 * 60 * 60; // 1h
 | 
				
			||||||
 | 
							res.cookie('signin_with_twitter_session_id', sessid, {
 | 
				
			||||||
 | 
								path: '/',
 | 
				
			||||||
 | 
								domain: `.${config.host}`,
 | 
				
			||||||
 | 
								secure: config.url.substr(0, 5) === 'https',
 | 
				
			||||||
 | 
								httpOnly: true,
 | 
				
			||||||
 | 
								expires: new Date(Date.now() + expires),
 | 
				
			||||||
 | 
								maxAge: expires
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							res.redirect(ctx.url);
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						app.get('/signin/twitter/cb', (req, res): any => {
 | 
				
			||||||
 | 
							// req.headers['cookie'] は常に string ですが、型定義の都合上
 | 
				
			||||||
 | 
							// string | string[] になっているので string を明示しています
 | 
				
			||||||
 | 
							const cookies = cookie.parse((req.headers['cookie'] as string || ''));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							const sessid = cookies['signin_with_twitter_session_id'];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (sessid == undefined) {
 | 
				
			||||||
 | 
								res.status(400).send('invalid session');
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							redis.get(sessid, async (_, ctx) => {
 | 
				
			||||||
 | 
								const result = await twAuthSignin.done(JSON.parse(ctx), req.query.oauth_verifier);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								const user = await User.findOne({
 | 
				
			||||||
 | 
									'twitter.user_id': result.userId
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if (user == null) {
 | 
				
			||||||
 | 
									res.status(404).send(`@${result.screenName}と連携しているMisskeyアカウントはありませんでした...`);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								signin(res, user, true);
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -157,6 +157,7 @@
 | 
				
			||||||
		</h1>
 | 
							</h1>
 | 
				
			||||||
		<mk-signin ref="signin"/>
 | 
							<mk-signin ref="signin"/>
 | 
				
			||||||
	</div>
 | 
						</div>
 | 
				
			||||||
 | 
						<a href={ _API_URL_ + '/signin/twitter' }>Twitterでサインイン</a>
 | 
				
			||||||
	<div class="divider"><span>or</span></div>
 | 
						<div class="divider"><span>or</span></div>
 | 
				
			||||||
	<button class="signup" onclick={ parent.signup }>新規登録</button><a class="introduction" onclick={ introduction }>Misskeyについて</a>
 | 
						<button class="signup" onclick={ parent.signup }>新規登録</button><a class="introduction" onclick={ introduction }>Misskeyについて</a>
 | 
				
			||||||
	<style>
 | 
						<style>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,6 @@
 | 
				
			||||||
<mk-entrance-signin>
 | 
					<mk-entrance-signin>
 | 
				
			||||||
	<mk-signin/>
 | 
						<mk-signin/>
 | 
				
			||||||
 | 
						<a href={ _API_URL_ + '/signin/twitter' }>Twitterでサインイン</a>
 | 
				
			||||||
	<div class="divider"><span>or</span></div>
 | 
						<div class="divider"><span>or</span></div>
 | 
				
			||||||
	<button class="signup" onclick={ parent.signup }>%i18n:mobile.tags.mk-entrance-signin.signup%</button><a class="introduction" onclick={ parent.introduction }>%i18n:mobile.tags.mk-entrance-signin.about%</a>
 | 
						<button class="signup" onclick={ parent.signup }>%i18n:mobile.tags.mk-entrance-signin.signup%</button><a class="introduction" onclick={ parent.introduction }>%i18n:mobile.tags.mk-entrance-signin.about%</a>
 | 
				
			||||||
	<style>
 | 
						<style>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue