github.com/soulteary/pocket-bookcase@v0.0.0-20240428065142-0b5a9a0fc98a/internal/view/login.html (about)

     1  <!DOCTYPE html>
     2  <html lang="en">
     3  
     4  <head>
     5  	<base href="$$.RootPath$$">
     6  	<title>Login - Shiori</title>
     7  
     8  	<meta charset="UTF-8">
     9  	<meta name="viewport" content="width=device-width, initial-scale=1.0">
    10  
    11  	<link rel="apple-touch-icon-precomposed" sizes="152x152" href="assets/res/apple-touch-icon-152x152.png">
    12  	<link rel="apple-touch-icon-precomposed" sizes="144x144" href="assets/res/apple-touch-icon-144x144.png">
    13  	<link rel="icon" type="image/png" href="assets/res/favicon-32x32.png" sizes="32x32">
    14  	<link rel="icon" type="image/png" href="assets/res/favicon-16x16.png" sizes="16x16">
    15  	<link rel="icon" type="image/x-icon" href="assets/res/favicon.ico">
    16  
    17  	<link href="assets/css/style.css" rel="stylesheet">
    18  
    19  	<script src="assets/js/vue.min.js"></script>
    20  	<script src="assets/js/url.min.js"></script>
    21  </head>
    22  
    23  <body>
    24  	<div id="login-scene" :class="{night: NightMode}">
    25  		<p class="error-message" v-if="error !== ''">{{error}}</p>
    26  		<div id="login-box">
    27  			<form @submit.prevent="login">
    28  				<div id="logo-area">
    29  					<p id="logo">
    30  						<span>栞</span>shiori
    31  					</p>
    32  					<p id="tagline">simple bookmark manager</p>
    33  				</div>
    34  				<div id="input-area">
    35  					<label for="username">Username: </label>
    36  					<input id="username" type="text" name="username" placeholder="Username" tabindex="1" autofocus />
    37  					<label for="password">Password: </label>
    38  					<input id="password" type="password" name="password" placeholder="Password" tabindex="2"
    39  						@keyup.enter="login">
    40  					<label class="checkbox-field"><input type="checkbox" name="remember" v-model="remember"
    41  							tabindex="3">Remember me</label>
    42  				</div>
    43  				<div id="button-area">
    44  					<a v-if="loading">
    45  						<i class="fas fa-fw fa-spinner fa-spin"></i>
    46  					</a>
    47  					<a v-else class="button" tabindex="4" @click="login" @keyup.enter="login">Log In</a>
    48  				</div>
    49  			</form>
    50  		</div>
    51  		<div>
    52  			<p>$$.Version$$</p>
    53  		</div>
    54  	</div>
    55  
    56  	<script type="module">
    57  		var app = new Vue({
    58  			el: "#login-scene",
    59  			data: {
    60  				error: "",
    61  				loading: false,
    62  				username: "",
    63  				password: "",
    64  				remember: false,
    65  				NightMode: false,
    66  			},
    67  			methods: {
    68  				async getErrorMessage(err) {
    69  					switch (err.constructor) {
    70  						case Error:
    71  							return err.message;
    72  						case Response:
    73  							var text = await err.text();
    74  
    75  							// Handle new error messages
    76  							if (text[0] == "{") {
    77  								var json = JSON.parse(text);
    78  								return json.message;
    79  							}
    80  							return `${text} (${err.status})`;
    81  						default:
    82  							return err;
    83  					}
    84  				},
    85  				login() {
    86  					function parseJWT(token) {
    87  						try {
    88  							return JSON.parse(atob(token.split('.')[1]));
    89  						} catch (e) {
    90  							return null;
    91  						}
    92  					}
    93  
    94  					// needed to work around autofill issue
    95  					// https://github.com/facebook/react/issues/1159#issuecomment-506584346
    96  					this.username = document.querySelector('#username').value;
    97  					this.password = document.querySelector('#password').value;
    98  					// Validate input
    99  					if (this.username === "") {
   100  						this.error = "Username must not empty";
   101  						return;
   102  					}
   103  
   104  					// Remove old cookie
   105  					document.cookie = `session-id=; Path=${new URL(document.baseURI).pathname}; Expires=Thu, 01 Jan 1970 00:00:00 GMT;`;
   106  
   107  					// Send request
   108  					this.loading = true;
   109  
   110  					fetch(new URL("api/v1/auth/login", document.baseURI), {
   111  						method: "post",
   112  						body: JSON.stringify({
   113  							username: this.username,
   114  							password: this.password,
   115  							remember_me: this.remember == 1 ? true : false,
   116  						}),
   117  						headers: { "Content-Type": "application/json" },
   118  					}).then(response => {
   119  						if (!response.ok) throw response;
   120  						return response.json();
   121  					}).then(json => {
   122  						// Save session id
   123  						document.cookie = `session-id=${json.message.session}; Path=${new URL(document.baseURI).pathname}; Expires=${json.message.expires}`;
   124  						document.cookie = `token=${json.message.token}; Path=${new URL(document.baseURI).pathname}; Expires=${json.message.expires}`;
   125  
   126  						// Save account data
   127  						localStorage.setItem("shiori-token", json.message.token);
   128  						localStorage.setItem("shiori-account", JSON.stringify(parseJWT(json.message.token).account));
   129  
   130  						// Go to destination page
   131  						var currentUrl = new Url(),
   132  							dstUrl = currentUrl.query.dst,
   133  							dstPage = currentUrl.hash || "home";
   134  
   135  						if (dstPage !== "home" && dstPage !== "setting") {
   136  							dstPage = "";
   137  						}
   138  
   139  						// TODO: Improve this redirect logic
   140  						var newUrl = new Url(dstUrl || document.baseURI);
   141  						newUrl.hash = dstPage;
   142  
   143  						location.href = newUrl;
   144  					}).catch(err => {
   145  						this.loading = false;
   146  						this.getErrorMessage(err).then(msg => {
   147  							this.error = msg;
   148  						})
   149  					});
   150  				},
   151  				loadSetting() {
   152  					var opts = JSON.parse(localStorage.getItem("shiori-account")) || {},
   153  						NightMode = (typeof opts.config.NightMode === "boolean") ? opts.configNightMode : false;
   154  
   155  					this.NightMode = NightMode;
   156  				}
   157  			},
   158  			mounted() {
   159  				// Load setting
   160  				this.loadSetting();
   161  				localStorage.removeItem("shiori-account");
   162  				localStorage.removeItem("shiori-token");
   163  
   164  				// <input autofocus> wasn't working all the time, so I'm putting this here as a fallback
   165  				document.querySelector('#username').focus()
   166  			}
   167  		})
   168  	</script>
   169  </body>
   170  
   171  </html>