727 lines
47 KiB
Handlebars
727 lines
47 KiB
Handlebars
<!DOCTYPE html><html dir="ltr" xmlns="http://www.w3.org/1999/xhtml"><head>
|
||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||
<meta content="text/html; charset=utf-8" http-equiv="Content-Type">
|
||
<meta name="viewport" content="user-scalable=1.0,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0">
|
||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||
<meta name="format-detection" content="telephone=no">
|
||
<link rel="shortcut icon" type="image/x-icon" href="{{{domainurl}}}favicon.ico">
|
||
<link keeplink="1" type="text/css" href="styles/style.css" media="screen" rel="stylesheet" title="CSS">
|
||
<script type="text/javascript" src="scripts/common-0.0.1.js"></script>
|
||
<script keeplink="1" type="text/javascript" src="scripts/u2f-api.js"></script>
|
||
<title>{{{title}}} - Login</title>
|
||
</head>
|
||
<body id="body" onload="if (typeof(startup) !== 'undefined') startup();" class="arg_hide login">
|
||
<div id="container">
|
||
<div id="masthead">
|
||
<div class="title">{{{title}}}</div>
|
||
<div class="title2">{{{title2}}}</div>
|
||
</div>
|
||
<div id="topbar" class="noselect style3" style="height:24px">
|
||
<div id="uiMenuButton" title="ユーザーインターフェイスの選択" onclick="showUserInterfaceSelectMenu()">
|
||
♦
|
||
<div id="uiMenu" style="display:none">
|
||
<div id="uiViewButton1" class="uiSelector" onclick="userInterfaceSelectMenu(1)" title="左バーインターフェイス"><div class="uiSelector1"></div></div>
|
||
<div id="uiViewButton2" class="uiSelector" onclick="userInterfaceSelectMenu(2)" title="トップバーインターフェース"><div class="uiSelector2"></div></div>
|
||
<div id="uiViewButton3" class="uiSelector" onclick="userInterfaceSelectMenu(3)" title="固定幅インターフェイス"><div class="uiSelector3"></div></div>
|
||
<div id="uiViewButton4" class="uiSelector" onclick="toggleNightMode()" title="ナイトモードを切り替える"><div class="uiSelector4"></div></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div id="column_l">
|
||
<h1>ようこそ</h1>
|
||
<div id="welcomeText" style="display:none">リアルタイムのオープンソースのリモート監視および管理WebサイトであるMeshCentralを使用して、世界中のどこからでも自宅またはオフィスのデバイスに接続します。コンピューターに管理エージェントをダウンロードしてインストールする必要があります。インストールすると、コンピューターはこのWebサイトの[デバイス]セクションに表示され、コンピューターを監視して制御できるようになります。</div>
|
||
<table id="centralTable" style="">
|
||
<tbody><tr>
|
||
<td id="welcomeimage">
|
||
<picture>
|
||
<img alt="" src="welcome.jpg" style="border-radius:20px">
|
||
</picture>
|
||
</td>
|
||
<td id="logincell">
|
||
<div id="loginpanel" style="display:none">
|
||
<form method="post">
|
||
<input type="hidden" name="action" value="login">
|
||
<div id="message1"></div>
|
||
<div>
|
||
<b>ログイン</b>
|
||
</div>
|
||
<table>
|
||
<tbody><tr>
|
||
<td id="loginusername" align="right" width="100">ユーザー名:</td>
|
||
<td><input id="username" type="text" maxlength="64" name="username" onchange="validateLogin(1)" onkeyup="validateLogin(1,event)"></td>
|
||
</tr>
|
||
<tr>
|
||
<td align="right">パスワード:</td>
|
||
<td><input id="password" type="password" maxlength="256" name="password" autocomplete="off" onchange="validateLogin(2)" onkeyup="validateLogin(2,event)"></td>
|
||
</tr>
|
||
<tr>
|
||
<td><div id="showPassHintLink" style="display:none"><a onclick="return showPassHint(event);" href="#" style="cursor:pointer">ヒントを見せて</a></div></td>
|
||
<td align="right"><input id="loginButton" type="submit" value="ログイン" disabled="disabled"></td>
|
||
</tr>
|
||
</tbody></table>
|
||
<div id="hrAccountDiv" style="display:none"><hr></div>
|
||
<div id="resetAccountDiv" style="display:none;padding:2px">
|
||
<span id="resetAccountSpan">ユーザー名/パスワードを忘れましたか?</span> <a onclick="return xgo(3,event);" href="#" style="cursor:pointer">アカウントをリセット</a>。
|
||
</div>
|
||
<div id="newAccountDiv" style="display:none;padding:2px">
|
||
アカウントを持っていないのですか? <a onclick="return xgo(2,event);" href="#" style="cursor:pointer">一つ作る</a>。
|
||
</div>
|
||
<input id="loginformargs" name="urlargs" type="hidden" value="">
|
||
</form>
|
||
</div>
|
||
<div id="createpanel" style="display:none;position:relative">
|
||
<form method="post">
|
||
<input type="hidden" name="action" value="createaccount">
|
||
<div id="message2"></div>
|
||
<div>
|
||
<b>アカウント作成</b>
|
||
</div>
|
||
<div id="passwordPolicyCallout" style="display:none"></div>
|
||
<table>
|
||
<tbody><tr id="nuUserRow">
|
||
<td id="nuUser" align="right" width="100">ユーザー名:</td>
|
||
<td><input id="ausername" type="text" name="username" onchange="validateCreate(1)" maxlength="64" onkeydown="haltReturn(event)" onkeyup="validateCreate(1,event)"></td>
|
||
</tr>
|
||
<tr>
|
||
<td id="nuEmail" align="right" width="100">Eメール:</td>
|
||
<td><input id="aemail" type="text" name="email" onchange="validateCreate(2)" maxlength="256" onkeydown="haltReturn(event)" onkeyup="validateCreate(2,event)"></td>
|
||
</tr>
|
||
<tr>
|
||
<td id="nuPass1" align="right">パスワード:</td>
|
||
<td><input id="apassword1" type="password" name="password1" autocomplete="off" maxlength="256" onkeydown="haltReturn(event)" onchange="validateCreate(3,event)" onkeyup="validateCreate(3,event)"></td>
|
||
</tr>
|
||
<tr>
|
||
<td id="nuPass2" align="right">パスワード:</td>
|
||
<td><input id="apassword2" type="password" name="password2" autocomplete="off" maxlength="256" onkeydown="haltReturn(event)" onchange="validateCreate(4,event)" onkeyup="validateCreate(4,event)"></td>
|
||
</tr>
|
||
<tr id="createPanelHint" style="display:none">
|
||
<td id="nuHint" align="right">パスワードのヒント:</td>
|
||
<td><input id="apasswordhint" type="text" name="apasswordhint" autocomplete="off" maxlength="256" onkeydown="haltReturn(event)" onchange="validateCreate(5,event)" onkeyup="validateCreate(5,event)"></td>
|
||
</tr>
|
||
<tr id="newAccountPass" title="アカウント作成トークンを入力してください">
|
||
<td id="nuToken" align="right">作成トークン:</td>
|
||
<td><input id="anewaccountpass" type="password" name="anewaccountpass" autocomplete="off" maxlength="256" onkeydown="haltReturn(event)" onchange="validateCreate(6,event)" onkeyup="validateCreate(6,event)"></td>
|
||
</tr>
|
||
<tr>
|
||
<td colspan="2">
|
||
<div style="float:right"><input id="createButton" type="submit" value="アカウントを作成する" disabled="disabled"></div>
|
||
<div id="passWarning" style="padding-top:6px"></div>
|
||
</td>
|
||
</tr>
|
||
</tbody></table>
|
||
<hr><a onclick="return xgo(1,event);" href="#" style="cursor:pointer">ログインに戻る</a>
|
||
<input id="createformargs" name="urlargs" type="hidden" value="">
|
||
</form>
|
||
</div>
|
||
<div id="resetpanel" style="display:none">
|
||
<form method="post">
|
||
<input type="hidden" name="action" value="resetaccount">
|
||
<div id="message3"></div>
|
||
<div>
|
||
<b>アカウントのリセット</b>
|
||
</div>
|
||
<table>
|
||
<tbody><tr>
|
||
<td align="right" width="100">Eメール:</td>
|
||
<td><input id="remail" type="text" name="email" maxlength="256" onchange="validateReset()" onkeyup="validateReset(event)"></td>
|
||
</tr>
|
||
<tr>
|
||
<td colspan="2">
|
||
<div style="float:right"><input id="eresetButton" type="submit" value="アカウントをリセット" disabled="disabled"></div>
|
||
<div id="passWarning" style="padding-top:6px"></div>
|
||
</td>
|
||
</tr>
|
||
</tbody></table>
|
||
<hr><a onclick="return xgo(1,event);" href="#" style="cursor:pointer">ログインに戻る</a>
|
||
<input id="resetformargs" name="urlargs" type="hidden" value="">
|
||
</form>
|
||
</div>
|
||
<div id="tokenpanel" style="display:none">
|
||
<form method="post" autocomplete="off">
|
||
<input type="hidden" name="action" value="tokenlogin">
|
||
<input type="hidden" name="hwstate" value="{{{hwstate}}}">
|
||
<div id="message4"></div>
|
||
<table>
|
||
<tbody><tr>
|
||
<td align="right" width="100">ログイントークン:</td>
|
||
<td>
|
||
<input id="tokenInput" type="text" name="token" maxlength="50" onchange="checkToken(event)" onpaste="resetCheckToken(event)" onkeyup="checkToken(event)" onkeydown="checkToken(event)"><br>
|
||
<input id="hwtokenInput" type="text" name="hwtoken" style="display:none">
|
||
</td>
|
||
</tr>
|
||
<tr>
|
||
<td colspan="2" style="align-content:center">
|
||
<label><input id="tokenInputRemember" name="remembertoken" type="checkbox">このデバイスを30日間記憶します。</label>
|
||
</td>
|
||
</tr>
|
||
<tr>
|
||
<td colspan="2">
|
||
<div style="float:right"><input id="tokenOkButton" type="submit" value="ログイン" disabled="disabled"></div>
|
||
<div style="float:right"><input style="display:none;float:right" id="securityKeyButton" type="button" value="セキュリティキーを使用" onclick="useSecurityKey()"></div>
|
||
</td>
|
||
</tr>
|
||
</tbody></table>
|
||
<hr><a onclick="return xgo(1,event);" href="#" style="cursor:pointer">ログインに戻る</a>
|
||
<input id="tokenformargs" name="urlargs" type="hidden" value="">
|
||
</form>
|
||
</div>
|
||
<div id="resettokenpanel" style="display:none">
|
||
<form method="post">
|
||
<input type="hidden" name="action" value="resetaccount">
|
||
<div id="message5"></div>
|
||
<table>
|
||
<tbody><tr>
|
||
<td align="right" width="100">ログイントークン:</td>
|
||
<td>
|
||
<input id="resetTokenInput" type="text" name="token" maxlength="50" onchange="resetCheckToken(event)" onkeyup="resetCheckToken(event)" onkeydown="resetCheckToken(event)">
|
||
<input id="resetHwtokenInput" type="text" name="hwtoken" style="display:none">
|
||
</td>
|
||
</tr>
|
||
<tr>
|
||
<td colspan="2">
|
||
<div style="float:right"><input id="resetTokenOkButton" type="submit" value="ログイン" disabled="disabled"></div>
|
||
</td>
|
||
</tr>
|
||
</tbody></table>
|
||
<hr><a onclick="return xgo(1,event);" href="#" style="cursor:pointer">ログインに戻る</a>
|
||
<input id="resettokenformargs" name="urlargs" type="hidden" value="">
|
||
</form>
|
||
</div>
|
||
<div id="resetpasswordpanel" style="display:none;position:relative">
|
||
<form method="post">
|
||
<input type="hidden" name="action" value="resetpassword">
|
||
<div id="message6"></div>
|
||
<div id="rpasswordPolicyCallout" style="display:none"></div>
|
||
<table>
|
||
<tbody><tr>
|
||
<td id="rnuPass1" width="100" align="right">パスワード:</td>
|
||
<td><input id="rapassword1" type="password" name="rpassword1" autocomplete="off" maxlength="256" onkeydown="haltReturn(event)" onchange="validatePassReset(3,event)" onkeyup="validatePassReset(3,event)"></td>
|
||
</tr>
|
||
<tr>
|
||
<td id="rnuPass2" align="right">パスワード:</td>
|
||
<td><input id="rapassword2" type="password" name="rpassword2" autocomplete="off" maxlength="256" onkeydown="haltReturn(event)" onchange="validatePassReset(4,event)" onkeyup="validatePassReset(4,event)"></td>
|
||
</tr>
|
||
<tr id="resetpasswordpanelHint" style="display:none">
|
||
<td id="rnuHint" align="right">パスワードのヒント:</td>
|
||
<td><input id="rapasswordhint" type="text" name="rpasswordhint" autocomplete="off" maxlength="256" onkeydown="haltReturn(event)" onchange="validatePassReset(5,event)" onkeyup="validatePassReset(5,event)"></td>
|
||
</tr>
|
||
<tr>
|
||
<td colspan="2">
|
||
<div style="float:right"><input id="resetPassButton" type="submit" value="パスワードを再設定する" disabled="disabled"></div>
|
||
<div id="rpassWarning" style="padding-top:6px"></div>
|
||
</td>
|
||
</tr>
|
||
</tbody></table>
|
||
<hr><a onclick="return xgo(1,event);" href="#" style="cursor:pointer">ログインに戻る</a>
|
||
<input id="resetpasswordformargs" name="urlargs" type="hidden" value="">
|
||
</form>
|
||
</div>
|
||
</td>
|
||
</tr>
|
||
</tbody></table>
|
||
<br>
|
||
</div>
|
||
<div id="footer">
|
||
<div class="footer1">{{{footer}}}</div>
|
||
<div class="footer2">
|
||
{{{rootCertLink}}}
|
||
<a href="terms">利用規約とプライバシー</a>
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
<div id="dialog" style="display:none">
|
||
<div id="dialogHeader">
|
||
<div id="id_dialogclose" style="float:right;padding:5px;cursor:pointer" onclick="setDialogMode()"><b>バツ</b></div>
|
||
<div id="id_dialogtitle" style="padding:5px"></div>
|
||
<div style="width:100%;margin:6px"></div>
|
||
</div>
|
||
<div id="dialogBody">
|
||
<div id="dialog1">
|
||
<div id="id_dialogMessage" style=""></div>
|
||
</div>
|
||
<div id="dialog2" style="">
|
||
<div id="id_dialogOptions"></div>
|
||
</div>
|
||
</div>
|
||
<div id="idx_dlgButtonBar" style="">
|
||
<input id="idx_dlgCancelButton" type="button" value="キャンセル" style="" onclick="dialogclose(0)">
|
||
<input id="idx_dlgOkButton" type="button" value="OK" style="" onclick="dialogclose(1)">
|
||
</div>
|
||
</div>
|
||
<script>
|
||
'use strict';
|
||
var passhint = '{{{passhint}}}';
|
||
var loginMode = '{{{loginmode}}}';
|
||
var newAccount = '{{{newAccount}}}';
|
||
var newAccountPass = parseInt('{{{newAccountPass}}}');
|
||
var emailCheck = ('{{{emailcheck}}}' == 'true');
|
||
var passRequirements = '{{{passRequirements}}}';
|
||
var hardwareKeyChallenge = decodeURIComponent('{{{hkey}}}');
|
||
if (passRequirements != '') { passRequirements = JSON.parse(decodeURIComponent(passRequirements)); } else { passRequirements = {}; }
|
||
var passRequirementsEx = ((passRequirements.min != null) || (passRequirements.max != null) || (passRequirements.upper != null) || (passRequirements.lower != null) || (passRequirements.numeric != null) || (passRequirements.nonalpha != null));
|
||
var features = parseInt('{{{features}}}');
|
||
var welcomeText = decodeURIComponent('{{{welcometext}}}');
|
||
var currentpanel = 0;
|
||
var uiMode = parseInt(getstore('uiMode', '1'));
|
||
var webPageFullScreen = true;
|
||
var nightMode = (getstore('_nightMode', '0') == '1');
|
||
var publicKeyCredentialRequestOptions = null;
|
||
|
||
// Display the right server message
|
||
var messageid = parseInt('{{{messageid}}}');
|
||
var okmessages = ['', "お待ちください、送信されたメールをリセットします。"];
|
||
var failmessages = ["アカウントを作成できません。", "アカウントの上限に達しました。", "このメールアドレスを持つ既存のアカウント。", "アカウント作成トークンが無効です。", "ユーザー名は既に存在します。", "パスワードが拒否されました。別のパスワードを使用してください。", "無効なメール。", "アカウントが見つかりませんでした。", "トークンが無効です。もう一度お試しください。", "メールを送信できません。", "アカウントがロックされました。", "アクセスが拒否されました。", "ログインに失敗しました。ユーザー名とパスワードを確認してください。", "パスワードの変更が要求されました。", "IPアドレスがブロックされています。しばらくしてからもう一度お試しください。"];
|
||
if (messageid > 0) {
|
||
var msg = '';
|
||
if ((messageid < 100) && (messageid < okmessages.length)) { msg = okmessages[messageid]; }
|
||
else if ((messageid >= 100) && ((messageid - 100) < failmessages.length)) { msg = failmessages[messageid - 100]; }
|
||
if (msg != '') {
|
||
if (messageid >= 100) { msg = ('<span class="msg error"><b style=color:#8C001A>' + msg + '<b></span><br /><br />'); } else { msg = ('<span class="msg success"><b>' + msg + '</b></span><br /><br />'); }
|
||
for (var i = 1; i < 7; i++) { QH('message' + i, msg); }
|
||
}
|
||
}
|
||
|
||
// If URL arguments are provided, add them to form posts
|
||
if (window.location.href.indexOf('?') > 0) {
|
||
var urlargs = window.location.href.substring(window.location.href.indexOf('?'));
|
||
Q('loginformargs').value = urlargs;
|
||
Q('createformargs').value = urlargs;
|
||
Q('resetformargs').value = urlargs;
|
||
Q('tokenformargs').value = urlargs;
|
||
Q('resettokenformargs').value = urlargs;
|
||
Q('resetpasswordformargs').value = urlargs;
|
||
}
|
||
|
||
//var webPageFullScreen = getstore('webPageFullScreen', true);
|
||
//if (webPageFullScreen == 'false') { webPageFullScreen = false; }
|
||
//if (webPageFullScreen == 'true') { webPageFullScreen = true; }
|
||
//toggleFullScreen();
|
||
|
||
function startup() {
|
||
if ((features & 32) == 0) {
|
||
// Guard against other site's top frames (web bugs).
|
||
var loc = null;
|
||
try { loc = top.location.toString().toLowerCase(); } catch (e) { }
|
||
if (top != self && (loc == null || top.active == false)) { top.location = self.location; return; }
|
||
}
|
||
|
||
if (features & 0x200000) { // Email is username
|
||
QH('loginusername', "Eメール:");
|
||
QH('resetAccountSpan', "パスワードをお忘れですか?");
|
||
QV('nuUserRow', false);
|
||
}
|
||
|
||
if (nightMode) { QC('body').add('night'); }
|
||
|
||
QV('createPanelHint', passRequirements.hint === true);
|
||
QV('resetpasswordpanelHint', passRequirements.hint === true);
|
||
|
||
// Display the welcome text
|
||
if (welcomeText) { QH('welcomeText', welcomeText); }
|
||
QH('welcomeText', addTextLink('MeshCentral', Q('welcomeText').innerHTML, 'http://www.meshcommander.com/meshcentral2'));
|
||
QV('welcomeText', true);
|
||
|
||
window.onresize = center;
|
||
center();
|
||
|
||
validateLogin();
|
||
validateCreate();
|
||
if (loginMode.length != 0) { go(parseInt(loginMode)); } else { go(1); }
|
||
QV('newAccountDiv', (newAccount === '1') || (newAccount === 'true')); // If new accounts are not allowed, don't display the new account link.
|
||
if ((passhint != null) && (passhint.length > 0)) { QV('showPassHintLink', true); }
|
||
QV('newAccountPass', (newAccountPass == 1));
|
||
QV('resetAccountDiv', (emailCheck == true));
|
||
QV('hrAccountDiv', (emailCheck == true) || (newAccountPass == 1));
|
||
|
||
if (loginMode == '4') {
|
||
try { if (hardwareKeyChallenge.length > 0) { hardwareKeyChallenge = JSON.parse(hardwareKeyChallenge); } else { hardwareKeyChallenge = null; } } catch (ex) { hardwareKeyChallenge = null }
|
||
QV('securityKeyButton', (hardwareKeyChallenge != null) && (hardwareKeyChallenge.type == 'webAuthn'));
|
||
}
|
||
|
||
if (loginMode == '5') {
|
||
try { if (hardwareKeyChallenge.length > 0) { hardwareKeyChallenge = JSON.parse(hardwareKeyChallenge); } else { hardwareKeyChallenge = null; } } catch (ex) { hardwareKeyChallenge = null }
|
||
if ((hardwareKeyChallenge != null) && (hardwareKeyChallenge.type == 'webAuthn')) {
|
||
if (typeof hardwareKeyChallenge.challenge == 'string') { hardwareKeyChallenge.challenge = Uint8Array.from(atob(hardwareKeyChallenge.challenge), function (c) { return c.charCodeAt(0) }).buffer; }
|
||
|
||
publicKeyCredentialRequestOptions = { challenge: hardwareKeyChallenge.challenge, allowCredentials: [], timeout: hardwareKeyChallenge.timeout }
|
||
for (var i = 0; i < hardwareKeyChallenge.keyIds.length; i++) {
|
||
publicKeyCredentialRequestOptions.allowCredentials.push(
|
||
{ id: Uint8Array.from(atob(hardwareKeyChallenge.keyIds[i]), function (c) { return c.charCodeAt(0) }), type: 'public-key', transports: ['usb', 'ble', 'nfc'], }
|
||
);
|
||
}
|
||
|
||
// New WebAuthn hardware keys
|
||
navigator.credentials.get({ publicKey: publicKeyCredentialRequestOptions }).then(
|
||
function (rawAssertion) {
|
||
var assertion = {
|
||
id: btoa(String.fromCharCode.apply(null, new Uint8Array(rawAssertion.rawId))),
|
||
clientDataJSON: btoa(String.fromCharCode.apply(null, new Uint8Array(rawAssertion.response.clientDataJSON))),
|
||
userHandle: btoa(String.fromCharCode.apply(null, new Uint8Array(rawAssertion.response.userHandle))),
|
||
signature: btoa(String.fromCharCode.apply(null, new Uint8Array(rawAssertion.response.signature))),
|
||
authenticatorData: btoa(String.fromCharCode.apply(null, new Uint8Array(rawAssertion.response.authenticatorData))),
|
||
};
|
||
Q('resetHwtokenInput').value = JSON.stringify(assertion);
|
||
QE('resetTokenOkButton', true);
|
||
Q('resetTokenOkButton').click();
|
||
},
|
||
function (error) { console.log('credentials-get error', error); }
|
||
);
|
||
}
|
||
}
|
||
|
||
// Setup the user interface in the right mode
|
||
userInterfaceSelectMenu();
|
||
}
|
||
|
||
// Use a hardware security key
|
||
function useSecurityKey() {
|
||
if ((hardwareKeyChallenge != null) && (hardwareKeyChallenge.type == 'webAuthn')) {
|
||
if (typeof hardwareKeyChallenge.challenge == 'string') { hardwareKeyChallenge.challenge = Uint8Array.from(atob(hardwareKeyChallenge.challenge), function (c) { return c.charCodeAt(0) }).buffer; }
|
||
|
||
publicKeyCredentialRequestOptions = { challenge: hardwareKeyChallenge.challenge, allowCredentials: [], timeout: hardwareKeyChallenge.timeout }
|
||
for (var i = 0; i < hardwareKeyChallenge.keyIds.length; i++) {
|
||
publicKeyCredentialRequestOptions.allowCredentials.push(
|
||
{ id: Uint8Array.from(atob(hardwareKeyChallenge.keyIds[i]), function (c) { return c.charCodeAt(0) }), type: 'public-key', transports: ['usb', 'ble', 'nfc'], }
|
||
);
|
||
}
|
||
|
||
// New WebAuthn hardware keys
|
||
navigator.credentials.get({ publicKey: publicKeyCredentialRequestOptions }).then(
|
||
function (rawAssertion) {
|
||
var assertion = {
|
||
id: btoa(String.fromCharCode.apply(null, new Uint8Array(rawAssertion.rawId))),
|
||
clientDataJSON: btoa(String.fromCharCode.apply(null, new Uint8Array(rawAssertion.response.clientDataJSON))),
|
||
userHandle: btoa(String.fromCharCode.apply(null, new Uint8Array(rawAssertion.response.userHandle))),
|
||
signature: btoa(String.fromCharCode.apply(null, new Uint8Array(rawAssertion.response.signature))),
|
||
authenticatorData: btoa(String.fromCharCode.apply(null, new Uint8Array(rawAssertion.response.authenticatorData))),
|
||
};
|
||
Q('hwtokenInput').value = JSON.stringify(assertion);
|
||
QE('tokenOkButton', true);
|
||
Q('tokenOkButton').click();
|
||
},
|
||
function (error) { console.log('credentials-get error', error); }
|
||
);
|
||
}
|
||
}
|
||
|
||
function showPassHint(e) {
|
||
messagebox("パスワードのヒント", passhint);
|
||
haltEvent(e);
|
||
return false;
|
||
}
|
||
|
||
function xgo(x, e) {
|
||
QV('message1', false);
|
||
QV('message2', false);
|
||
QV('message3', false);
|
||
QV('message4', false);
|
||
QV('message5', false);
|
||
QV('message6', false);
|
||
go(x);
|
||
haltEvent(e);
|
||
return false;
|
||
}
|
||
|
||
function go(x) {
|
||
currentpanel = x;
|
||
setDialogMode(0);
|
||
QV('showPassHintLink', false);
|
||
QV('loginpanel', x == 1);
|
||
QV('createpanel', x == 2);
|
||
QV('resetpanel', x == 3);
|
||
QV('tokenpanel', x == 4);
|
||
QV('resettokenpanel', x == 5);
|
||
QV('resetpasswordpanel', x == 6);
|
||
if (x == 1) { Q('username').focus(); }
|
||
if (x == 2) { if (features & 0x200000) { Q('aemail').focus(); } else { Q('ausername').focus(); } } // Email is username
|
||
if (x == 3) { Q('remail').focus(); }
|
||
if (x == 4) { Q('tokenInput').focus(); }
|
||
if (x == 5) { Q('resetTokenInput').focus(); }
|
||
if (x == 6) { Q('rapassword1').focus(); }
|
||
}
|
||
|
||
function validateLogin(box, e) {
|
||
var ok = ((Q('username').value.length > 0) && (Q('username').value.indexOf(' ') == -1) && (Q('password').value.length > 0));
|
||
QE('loginButton', ok);
|
||
setDialogMode(0);
|
||
if ((e != null) && (e.keyCode == 13)) { if ((box == 1) && (Q('username').value != '')) { Q('password').focus(); } else if ((box == 2) && (Q('password').value != '')) { Q('loginButton').click(); } }
|
||
if (e != null) { haltEvent(e); }
|
||
}
|
||
|
||
function validateCreate(box, e) {
|
||
setDialogMode(0);
|
||
var userok = false;
|
||
if (features & 0x200000) { userok = true; } else { userok = (Q('ausername').value.length > 0) && (Q('ausername').value.indexOf(' ') == -1) && (Q('ausername').value.indexOf('"') == -1) && (Q('ausername').value.indexOf(',') == -1); }
|
||
var emailok = (validateEmail(Q('aemail').value) == true);
|
||
var pass1ok = (Q('apassword1').value.length > 0);
|
||
var pass2ok = (Q('apassword2').value.length > 0) && (Q('apassword2').value == Q('apassword1').value);
|
||
var newAccOk = (newAccountPass == 0) || (Q('anewaccountpass').value.length > 0);
|
||
var ok = (userok && emailok && pass1ok && pass2ok && newAccOk);
|
||
|
||
// Color the fields
|
||
QS('nuUser').color = userok?'black':'#7b241c';
|
||
QS('nuEmail').color = emailok?'black':'#7b241c';
|
||
QS('nuPass1').color = pass1ok?'black':'#7b241c';
|
||
QS('nuPass2').color = pass2ok?'black':'#7b241c';
|
||
QS('nuToken').color = newAccOk?'black':'#7b241c';
|
||
|
||
if (Q('apassword1').value == '') {
|
||
QH('passWarning', '');
|
||
QV('passwordPolicyCallout', false);
|
||
} else {
|
||
if (!passRequirementsEx) {
|
||
// No password requirements, display password strength
|
||
var passStrength = checkPasswordStrength(Q('apassword1').value);
|
||
if (passStrength >= 80) { QH('passWarning', '<span style=color:green><b>' + "強力なパスワード" + '</b><span>'); }
|
||
else if (passStrength >= 60) { QH('passWarning', '<span style=color:blue><b>' + "良いパスワード" + '</b><span>'); }
|
||
else { QH('passWarning', '<span style=color:red><b>' + "弱いパスワード" + '</b><span>'); }
|
||
} else {
|
||
// Password requirements provided, use that
|
||
var passReq = checkPasswordRequirements(Q('apassword1').value, passRequirements);
|
||
if (passReq == false) {
|
||
ok = false;
|
||
QS('nuPass1').color = '#7b241c';
|
||
QS('nuPass2').color = '#7b241c';
|
||
QH('passWarning', '<div style=color:red;cursor:pointer onclick=showPasswordPolicy()><b>' + "パスワードポリシー" + '</b><div>'); // This is also a link to the password policy
|
||
QV('passwordPolicyCallout', true);
|
||
QH('passwordPolicyCallout', passwordPolicyText(Q('apassword1').value));
|
||
} else {
|
||
QH('passWarning', '');
|
||
QV('passwordPolicyCallout', false);
|
||
}
|
||
}
|
||
}
|
||
if ((e != null) && (e.keyCode == 13)) {
|
||
|
||
if ((box == 1) && userok) { Q('aemail').focus(); }
|
||
if ((box == 2) && emailok) { Q('apassword1').focus(); }
|
||
if ((box == 3) && pass1ok) { Q('apassword2').focus(); }
|
||
if ((box == 4) && pass2ok) { if (passRequirements.hint === true) { Q('apasswordhint').focus(); } else { box = 5; } }
|
||
if (box == 5) { if (newAccountPass == 1) { Q('anewaccountpass').focus(); } else { box = 6; } }
|
||
if (box == 6) { Q('createButton').click(); }
|
||
}
|
||
if (e != null) { haltEvent(e); }
|
||
QE('createButton', ok);
|
||
}
|
||
|
||
function validatePassReset(box, e) {
|
||
setDialogMode(0);
|
||
var pass1ok = (Q('rapassword1').value.length > 0);
|
||
var pass2ok = (Q('rapassword2').value.length > 0) && (Q('rapassword2').value == Q('rapassword1').value);
|
||
var ok = (pass1ok && pass2ok);
|
||
|
||
// Color the fields
|
||
QS('rnuPass1').color = pass1ok ? 'black' : '#7b241c';
|
||
QS('rnuPass2').color = pass2ok ? 'black' : '#7b241c';
|
||
|
||
if (Q('rapassword1').value == '') {
|
||
QH('rpassWarning', '');
|
||
QV('rpasswordPolicyCallout', false);
|
||
} else {
|
||
if (!passRequirementsEx) {
|
||
// No password requirements, display password strength
|
||
var passStrength = checkPasswordStrength(Q('rapassword1').value);
|
||
if (passStrength >= 80) { QH('rpassWarning', '<span style=color:green><b>' + "強力なパスワード" + '</b><span>'); }
|
||
else if (passStrength >= 60) { QH('rpassWarning', '<span style=color:blue><b>' + "良いパスワード" + '</b><span>'); }
|
||
else { QH('rpassWarning', '<span style=color:red><b>' + "弱いパスワード" + '</b><span>'); }
|
||
} else {
|
||
// Password requirements provided, use that
|
||
var passReq = checkPasswordRequirements(Q('rapassword1').value, passRequirements);
|
||
if (passReq == false) {
|
||
ok = false;
|
||
QS('rnuPass1').color = '#7b241c';
|
||
QS('rnuPass2').color = '#7b241c';
|
||
QH('rpassWarning', '<div style=color:red;cursor:pointer onclick=showPasswordPolicy()><b>' + "パスワードポリシー" + '</b><div>'); // This is also a link to the password policy
|
||
QV('rpasswordPolicyCallout', true);
|
||
QH('rpasswordPolicyCallout', passwordPolicyText(Q('rapassword1').value));
|
||
} else {
|
||
QH('rpassWarning', '');
|
||
QV('rpasswordPolicyCallout', false);
|
||
}
|
||
}
|
||
}
|
||
if ((e != null) && (e.keyCode == 13)) {
|
||
if (box == 2) { Q('rapassword1').focus(); }
|
||
if (box == 3) { Q('rapassword2').focus(); }
|
||
if (box == 4) { Q('rapasswordhint').focus(); }
|
||
if (box == 6) { Q('resetPassButton').click(); }
|
||
}
|
||
if (e != null) { haltEvent(e); }
|
||
QE('resetPassButton', ok);
|
||
}
|
||
|
||
function passwordPolicyText(pass) {
|
||
var policy = '<div style=text-align:left>';
|
||
var counts = strCount(pass);
|
||
if (passRequirements.min && ((pass == null) || (pass.length < passRequirements.min))) { policy += format("最小長{0}", passRequirements.min) + '<br />'; }
|
||
if (passRequirements.max && ((pass == null) || (pass.length > passRequirements.max))) { policy += format("{0}の最大長", passRequirements.max) + '<br />'; }
|
||
if (passRequirements.upper && ((pass == null) || (counts.upper < passRequirements.upper))) { policy += format("{0}大文字", passRequirements.upper) + '<br />'; }
|
||
if (passRequirements.lower && ((pass == null) || (counts.lower < passRequirements.lower))) { policy += format("{0}小文字", passRequirements.lower) + '<br />'; }
|
||
if (passRequirements.numeric && ((pass == null) || (counts.numeric < passRequirements.numeric))) { policy += format("{0}数値", passRequirements.numeric) + '<br />'; }
|
||
if (passRequirements.nonalpha && ((pass == null) || (counts.nonalpha < passRequirements.nonalpha))) { policy += format("{0}英数字以外", passRequirements.nonalpha) + '<br />'; }
|
||
policy += '</div>';
|
||
return policy;
|
||
}
|
||
|
||
function showPasswordPolicy() {
|
||
messagebox("パスワードポリシー", passwordPolicyText());
|
||
}
|
||
|
||
function validateReset(e) {
|
||
setDialogMode(0);
|
||
var x = validateEmail(Q('remail').value);
|
||
QE('eresetButton', x);
|
||
if ((e != null) && (e.keyCode == 13) && (x == true)) {
|
||
Q('eresetButton').click();
|
||
}
|
||
if (e != null) { haltEvent(e); }
|
||
}
|
||
|
||
// Return a password strength score
|
||
function checkPasswordStrength(password) {
|
||
var r = 0, letters = {}, varCount = 0, variations = { digits: /\d/.test(password), lower: /[a-z]/.test(password), upper: /[A-Z]/.test(password), nonWords: /\W/.test(password) }
|
||
if (!password) return 0;
|
||
for (var i = 0; i< password.length; i++) { letters[password[i]] = (letters[password[i]] || 0) + 1; r += 5.0 / letters[password[i]]; }
|
||
for (var c in variations) { varCount += (variations[c] == true) ? 1 : 0; }
|
||
return parseInt(r + (varCount - 1) * 10);
|
||
}
|
||
|
||
// Check password requirements
|
||
function checkPasswordRequirements(password, requirements) {
|
||
if ((requirements == null) || (requirements == '') || (typeof requirements != 'object')) return true;
|
||
if (requirements.min) { if (password.length < requirements.min) return false; }
|
||
if (requirements.max) { if (password.length > requirements.max) return false; }
|
||
var counts = strCount(password);
|
||
if (requirements.numeric && (counts.numeric < requirements.numeric)) return false;
|
||
if (requirements.lower && (counts.lower < requirements.lower)) return false;
|
||
if (requirements.upper && (counts.upper < requirements.upper)) return false;
|
||
if (requirements.nonalpha && (counts.nonalpha < requirements.nonalpha)) return false;
|
||
return true;
|
||
}
|
||
|
||
function strCount(password) {
|
||
var counts = { numeric: 0, lower: 0, upper: 0, nonalpha: 0 };
|
||
if (typeof password != 'string') return counts;
|
||
for (var i = 0; i < password.length; i++) {
|
||
if (/\d/.test(password[i])) { counts.numeric++; }
|
||
if (/[a-z]/.test(password[i])) { counts.lower++; }
|
||
if (/[A-Z]/.test(password[i])) { counts.upper++; }
|
||
if (/\W/.test(password[i])) { counts.nonalpha++; }
|
||
}
|
||
return counts;
|
||
}
|
||
|
||
function checkToken() {
|
||
var t1 = Q('tokenInput').value;
|
||
var t2 = t1.split(' ').join('');
|
||
if (t1 != t2) { Q('tokenInput').value = t2; }
|
||
QE('tokenOkButton', (Q('tokenInput').value.length == 6) || (Q('tokenInput').value.length == 8) || (Q('tokenInput').value.length == 44));
|
||
}
|
||
|
||
function resetCheckToken() {
|
||
var t1 = Q('resetTokenInput').value;
|
||
var t2 = t1.split(' ').join('');
|
||
if (t1 != t2) { Q('resetTokenInput').value = t2; }
|
||
QE('resetTokenOkButton', (Q('resetTokenInput').value.length == 6) || (Q('resetTokenInput').value.length == 8) || (Q('resetTokenInput').value.length == 44));
|
||
}
|
||
|
||
//
|
||
// POPUP DIALOG
|
||
//
|
||
|
||
// undefined = Hidden, 1 = Generic Message
|
||
var xxdialogMode;
|
||
var xxdialogFunc;
|
||
var xxdialogButtons;
|
||
var xxdialogTag;
|
||
var xxcurrentView = 0;
|
||
|
||
// Display a dialog box
|
||
// Parameters: Dialog Mode (0 = none), Dialog Title, Buttons (1 = OK, 2 = Cancel, 3 = OK & Cancel), Call back function(0 = Cancel, 1 = OK), Dialog Content (Mode 2 only)
|
||
function setDialogMode(x, y, b, f, c, tag) {
|
||
xxdialogMode = x;
|
||
xxdialogFunc = f;
|
||
xxdialogButtons = b;
|
||
xxdialogTag = tag;
|
||
QE('idx_dlgOkButton', true);
|
||
QV('idx_dlgOkButton', b & 1);
|
||
QV('idx_dlgCancelButton', b & 2);
|
||
QV('id_dialogclose', (b & 2) || (b & 8));
|
||
QV('idx_dlgButtonBar', b & 7);
|
||
if (y) QH('id_dialogtitle', y);
|
||
for (var i = 1; i < 24; i++) { QV('dialog' + i, i == x); } // Edit this line when more dialogs are added
|
||
QV('dialog', x);
|
||
if (c) { if (x == 2) { QH('id_dialogOptions', c); } else { QH('id_dialogMessage', c); } }
|
||
}
|
||
|
||
function dialogclose(x) {
|
||
var f = xxdialogFunc;
|
||
var b = xxdialogButtons;
|
||
var t = xxdialogTag;
|
||
setDialogMode();
|
||
if (((b & 8) || x) && f) f(x, t);
|
||
}
|
||
|
||
// Toggle the web page to full screen
|
||
function toggleFullScreen(toggle) {
|
||
//if (toggle === 1) { webPageFullScreen = !webPageFullScreen; putstore('webPageFullScreen', webPageFullScreen); }
|
||
if (webPageFullScreen == false) {
|
||
// By adding body class, it will change a style of all ellements using CSS selector
|
||
// No need for JS anymore and it will be consistent style for all the templates.
|
||
QC('body').remove('fullscreen');
|
||
} else {
|
||
QC('body').add('fullscreen');
|
||
}
|
||
QV('body', true);
|
||
center();
|
||
}
|
||
|
||
// Toggle user interface menu
|
||
function showUserInterfaceSelectMenu() {
|
||
Q('uiViewButton1').classList.remove('uiSelectorSel');
|
||
Q('uiViewButton2').classList.remove('uiSelectorSel');
|
||
Q('uiViewButton3').classList.remove('uiSelectorSel');
|
||
try { Q('uiViewButton' + uiMode).classList.add('uiSelectorSel'); } catch (ex) { }
|
||
QV('uiMenu', (QS('uiMenu').display == 'none'));
|
||
if (nightMode) { Q('uiViewButton4').classList.add('uiSelectorSel'); }
|
||
}
|
||
|
||
function userInterfaceSelectMenu(s) {
|
||
if (s) { uiMode = s; putstore('uiMode', uiMode); }
|
||
webPageFullScreen = (uiMode < 3);
|
||
//webPageStackMenu = (uiMode > 1);
|
||
toggleFullScreen(0);
|
||
//toggleStackMenu(0);
|
||
}
|
||
|
||
function toggleNightMode() {
|
||
nightMode = !nightMode;
|
||
if (nightMode) { QC('body').add('night'); } else { QC('body').remove('night'); }
|
||
putstore('_nightMode', (nightMode ? '1' : '0'));
|
||
}
|
||
|
||
function center() {
|
||
/* Now we use CSS media to achive the same effect as deleted JS */
|
||
if (webPageFullScreen == false) {
|
||
QS('centralTable')['margin-top'] = '';
|
||
} else {
|
||
var h = ((Q('column_l').clientHeight) / 2) - 220;
|
||
if (h < 0) h = 0;
|
||
QS('centralTable')['margin-top'] = h + 'px';
|
||
}
|
||
}
|
||
function messagebox(t, m) { QH('id_dialogMessage', m); setDialogMode(1, t, 1); }
|
||
function statusbox(t, m) { QH('id_dialogMessage', m); setDialogMode(1, t); }
|
||
function getDocWidth() { if (window.innerWidth) return window.innerWidth; if (document.documentElement && document.documentElement.clientWidth && document.documentElement.clientWidth != 0) return document.documentElement.clientWidth; return document.getElementsByTagName('body')[0].clientWidth; }
|
||
function haltEvent(e) { if (e.preventDefault) e.preventDefault(); if (e.stopPropagation) e.stopPropagation(); return false; }
|
||
function haltReturn(e) { if (e.keyCode == 13) { haltEvent(e); } }
|
||
function validateEmail(v) { var emailReg = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; return emailReg.test(v); } // New version
|
||
function putstore(name, val) { try { if (typeof (localStorage) === 'undefined') return; localStorage.setItem(name, val); } catch (e) { } }
|
||
function getstore(name, val) { try { if (typeof (localStorage) === 'undefined') return val; var v = localStorage.getItem(name); if ((v == null) || (v == null)) return val; return v; } catch (e) { return val; } }
|
||
function format(format) { var args = Array.prototype.slice.call(arguments, 1); return format.replace(/{(\d+)}/g, function (match, number) { return typeof args[number] != 'undefined' ? args[number] : match; }); };
|
||
function addTextLink(subtext, text, link) { var i = text.toLowerCase().indexOf(subtext.toLowerCase()); if (i == -1) { return text; } return text.substring(0, i) + '<a href=\"' + link + '\">' + subtext + '</a>' + text.substring(i + subtext.length); }
|
||
|
||
</script>
|
||
|
||
</body></html> |