0
0
Fork 0

インスタンスURLのバリデーション

This commit is contained in:
Xeltica 2022-06-21 22:23:53 +09:00
parent 9d7f78856d
commit 9cc696f87a
6 changed files with 43 additions and 11 deletions

View file

@ -29,7 +29,17 @@ router.get('/login', async ctx => {
return; return;
} }
const meta = await api<{ name: string, uri: string, version: string, features: Record<string, boolean | undefined> }>(host, 'meta', {}); // http://, https://を潰す
host = host.trim().replace(/^https?:\/\//g, '').replace(/\/+/g, '');
const meta = await api<{ name: string, uri: string, version: string, features: Record<string, boolean | undefined> }>(host, 'meta', {}).catch(async e => {
if (!(e instanceof Error && e.name === 'Error')) throw e;
await die(ctx, 'hostNotFound');
});
// NOTE: catchが呼ばれた場合はvoidとなるためundefinedのはず
if (typeof meta === 'undefined') return;
if (typeof meta !== 'object') { if (typeof meta !== 'object') {
await die(ctx, 'other'); await die(ctx, 'other');
return; return;
@ -40,9 +50,6 @@ router.get('/login', async ctx => {
return; return;
} }
// ホスト名の正規化
host = meta.uri.replace(/^https?:\/\//, '');
const { name, permission, description } = misskeyAppInfo; const { name, permission, description } = misskeyAppInfo;
if (meta.features.miauth) { if (meta.features.miauth) {

View file

@ -5,6 +5,8 @@ export const errorCodes = [
'tokenRequired', 'tokenRequired',
'invalidParamater', 'invalidParamater',
'notAuthorized', 'notAuthorized',
'hostNotFound',
'invalidHostFormat',
'other', 'other',
] as const; ] as const;

View file

@ -1,5 +1,5 @@
import React, { useEffect } from 'react'; import React, { useEffect, useState } from 'react';
import { BrowserRouter, useLocation } from 'react-router-dom'; import { BrowserRouter, Link, useLocation } from 'react-router-dom';
import { Provider, useDispatch } from 'react-redux'; import { Provider, useDispatch } from 'react-redux';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@ -22,7 +22,18 @@ const AppInner : React.VFC = () => {
const {t} = useTranslation(); const {t} = useTranslation();
const error = (window as any).__misshaialert?.error; const [error, setError] = useState<any>((window as any).__misshaialert?.error);
// ページ遷移がまだされていないかどうか
const [isFirstView, setFirstView] = useState(true);
useEffect(() => {
if (isFirstView) {
setFirstView(false);
} else if (!isFirstView && error) {
setError(null);
}
}, [$location]);
useEffect(() => { useEffect(() => {
const qMobile = window.matchMedia(`(max-width: ${BREAKPOINT_SM})`); const qMobile = window.matchMedia(`(max-width: ${BREAKPOINT_SM})`);
@ -47,6 +58,7 @@ const AppInner : React.VFC = () => {
{t('_error.additionalInfo')} {t('_error.additionalInfo')}
{t(`_error.${error}`)} {t(`_error.${error}`)}
</p> </p>
<Link to="/" className="btn primary">{t('retry')}</Link>
</div> </div>
) : <Router />} ) : <Router />}
<footer className="text-center pa-5"> <footer className="text-center pa-5">

View file

@ -11,6 +11,10 @@ export const LoginForm: React.VFC = () => {
const [host, setHost] = useState(''); const [host, setHost] = useState('');
const {t} = useTranslation(); const {t} = useTranslation();
const login = () => {
location.href = `//${location.host}/login?host=${encodeURIComponent(host)}`;
};
return ( return (
<nav> <nav>
<div> <div>
@ -21,14 +25,18 @@ export const LoginForm: React.VFC = () => {
className="input-field" className="input-field"
type="text" type="text"
value={host} value={host}
placeholder={t('instanceUrlPlaceholder')}
onChange={(e) => setHost(e.target.value)} onChange={(e) => setHost(e.target.value)}
onKeyDown={(e) => {
if (e.key === 'Enter') login();
}}
required required
/> />
<button <button
className={!host ? 'btn' : 'btn primary'} className={!host ? 'btn' : 'btn primary'}
style={{ width: 128 }} style={{ width: 128 }}
disabled={!host} disabled={!host}
onClick={() => location.href = `//${location.host}/login?host=${encodeURIComponent(host)}`} onClick={login}
> >
{t('login')} {t('login')}
</button> </button>

View file

@ -49,6 +49,7 @@
"update": "更新する", "update": "更新する",
"shareMisskeyTools": "#MisskeyTools をシェアする", "shareMisskeyTools": "#MisskeyTools をシェアする",
"shareMisskeyToolsNote": "#MisskeyTools はいいぞ\n\nhttps://misskey.tools", "shareMisskeyToolsNote": "#MisskeyTools はいいぞ\n\nhttps://misskey.tools",
"instanceUrlPlaceholder": "例misskey.io",
"_sidebar": { "_sidebar": {
"dashboard": "ダッシュボード", "dashboard": "ダッシュボード",
"tools": "ツール", "tools": "ツール",
@ -158,7 +159,8 @@
"tokenRequired": "トークンがありません。", "tokenRequired": "トークンがありません。",
"invalidParameter": "パラメータが不正です。", "invalidParameter": "パラメータが不正です。",
"notAuthorized": "権限がありません。", "notAuthorized": "権限がありません。",
"other": "なし" "hostNotFound": "インスタンスに接続できませんでした。ホスト名が正しく入力されているか、接続先のインスタンスが正常に動作しているかご確認ください。",
"other": "不明なエラーです。"
}, },
"_sendTest": { "_sendTest": {
"title": "アラートをテスト送信しますか?", "title": "アラートをテスト送信しますか?",

View file

@ -14,6 +14,7 @@ import { Skeleton } from '../../components/Skeleton';
import './misshai.scss'; import './misshai.scss';
import { Ranking } from '../../components/Ranking'; import { Ranking } from '../../components/Ranking';
import { useTitle } from '../../hooks/useTitle'; import { useTitle } from '../../hooks/useTitle';
import { Link } from 'react-router-dom';
const variables = [ const variables = [
'notesCount', 'notesCount',
@ -236,8 +237,8 @@ export const MisshaiPage: React.VFC = () => {
<div className="card misshaiRanking"> <div className="card misshaiRanking">
<div className="body"> <div className="body">
<h1><i className="bi bi-bar-chart"></i> {t('_missHai.ranking')}</h1> <h1><i className="bi bi-bar-chart"></i> {t('_missHai.ranking')}</h1>
<Ranking limit={limit} /> <Ranking limit={10} />
{limit && <button className="btn primary" onClick={() => setLimit(undefined)}>{t('_missHai.showAll')}</button>} <Link to="/apps/miss-hai/ranking" className="btn primary" onClick={() => setLimit(undefined)}>{t('_missHai.showAll')}</Link>
<label className="input-check mt-2"> <label className="input-check mt-2">
<input type="checkbox" checked={draft.useRanking} onChange={(e) => { <input type="checkbox" checked={draft.useRanking} onChange={(e) => {
updateSetting({ useRanking: e.target.checked }); updateSetting({ useRanking: e.target.checked });