Skip to content

Commit 75d7405

Browse files
implement "remember me" with same adapter pattern as the core (hardcoded index.html adapter ingratiates itself to common interface). implemented localstorage adapter and the corresponding mock core endpoints. seems to be working great. moving core-adapters/* to adapters/core/* and adding adapters/remember-me/ lol
1 parent e26f3b0 commit 75d7405

File tree

11 files changed

+167
-47
lines changed

11 files changed

+167
-47
lines changed

index.html

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@
1212
</head>
1313
<body>
1414
<div id="app"></div>
15-
<script type="module" src="/src/core-adapters/mock.js"></script>
15+
<script type="module" src="/src/adapters/core/mock.js"></script>
16+
<script type="module" src="/src/adapters/remember-me/localstorage.js"></script>
1617
<script type="module" src="/src/main.js"></script>
1718
</body>
1819
</html>

src/App.svelte

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,12 @@
66
import { pages, pop_page, set_page } from './models/pages';
77
import * as core from './models/turtl/core';
88
import user from './models/turtl/user';
9+
import * as remember_me from './models/remember-me';
910
import config from './models/turtl/config';
1011
import darkmode from './models/darkmode';
1112
import { procerr } from './util/error';
1213
import log from './util/log';
14+
import delay from './util/delay';
1315
import { init as i18n_init, loc } from './util/i18n';
1416
import * as shortcuts from './util/shortcuts';
1517
import { onMount } from 'svelte';
@@ -53,6 +55,8 @@
5355
'backspace': pop_page,
5456
});
5557
shortcuts.bind();
58+
await remember_me.login();
59+
await delay(50);
5660
user.subscribe(test_login);
5761
} catch(err) {
5862
set_page(Err, {params: {

src/core-adapters/mock.js renamed to src/adapters/core/mock.js

Lines changed: 44 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
1-
import * as core from '../models/turtl/core';
2-
import delay from '../util/delay';
3-
import Emitter from '../util/event';
4-
import log from '../util/log';
1+
import * as core from '@/models/turtl/core';
2+
import delay from '@/util/delay';
3+
import Emitter from '@/util/event';
4+
import log from '@/util/log';
55

66
const CoreAdapter = ((events) => {
77
const state = {
88
user: {
99
id: '01626b759c146c6f6b696da1b7e38fd92ff72c531689872a9da4e1b4cb7697b0636b15616a0f0017',
10-
username: 'login@turtl.app',
10+
username: 'user@turtl.app',
1111
password: 'password',
1212
confirmed: false,
1313
name: 'Andrew',
@@ -93,33 +93,53 @@ const CoreAdapter = ((events) => {
9393
response = {};
9494
break;
9595
}
96+
case 'profile:load': {
97+
await delay(500);
98+
event('profile:loaded');
99+
await delay(500);
100+
event('profile:indexed');
101+
response = state;
102+
break;
103+
}
104+
case 'sync:start': {
105+
break;
106+
}
96107
case 'user:login': {
97-
const [username, passphrase] = args;
98-
if(username === 'login@turtl.app' && passphrase === 'password') {
99-
response = {
100-
id: '01626b759c146c6f6b696da1b7e38fd92ff72c531689872a9da4e1b4cb7697b0636b15616a0f0017',
101-
username: 'login@turtl.app',
102-
passphrase: 'password',
103-
confirmed: false,
104-
name: 'Andrew',
105-
pubkey: null,
106-
settings: {},
107-
privkey: null,
108-
};
108+
const [username, password] = args;
109+
if(username === state.user.username && password === state.user.password) {
110+
state.user.loggedin = true;
111+
response = state.user;
109112
} else {
110113
error = {type: 'login_failed', message: `Login failed`};
111114
}
112115
break;
113116
}
114-
case 'profile:load': {
115-
await delay(1000);
116-
event('profile:loaded');
117-
await delay(2000);
118-
event('profile:indexed');
119-
response = state;
117+
case 'user:login-from-saved': {
118+
const [user_id, key] = args;
119+
const decoded = JSON.parse(key);
120+
if(decoded.u === state.user.username && decoded.p === state.user.password) {
121+
state.user.loggedin = true;
122+
response = state.user;
123+
} else {
124+
error = {type: 'login_failed', message: `Login failed`};
125+
}
120126
break;
121127
}
122-
case 'sync:start': {
128+
case 'user:logout': {
129+
state.user.loggedin = false;
130+
response = {};
131+
break;
132+
}
133+
case 'user:save-login': {
134+
if(state.user.loggedin) {
135+
response = {
136+
user_id: state.user.id,
137+
// TODO: ROT13 this and possibly base64
138+
key: JSON.stringify({u: state.user.username, p: state.user.password}),
139+
};
140+
} else {
141+
error = {type: 'missing_field', message: 'Turtl.user_id'};
142+
}
123143
break;
124144
}
125145
default: {
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import * as remember_me from '@/models/remember-me';
2+
3+
const KEY = 'remember_me';
4+
5+
remember_me.init({
6+
save: async (user_id, key) => {
7+
const val = {user_id, key};
8+
localStorage.setItem(KEY, JSON.stringify(val));
9+
},
10+
11+
get: async () => {
12+
const val = localStorage.getItem(KEY);
13+
if(!val) return null;
14+
return JSON.parse(val);
15+
},
16+
17+
clear: async() => {
18+
delete localStorage.removeItem(KEY);
19+
},
20+
});
21+

src/components/pages/Login.svelte

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
<script>
2+
import app_config from '@/config';
23
import Settings from './Settings.svelte';
34
import Button from '../form/Button.svelte';
45
import Input from '../form/Input.svelte';
@@ -27,6 +28,7 @@
2728
let passphrase = null;
2829
let passphrase_confirm = null;
2930
let i_understand = false;
31+
let remember_me = false;
3032
let turtl_server = null;
3133
let proxy_server = null;
3234
let skip_ssl_verification = false;
@@ -41,7 +43,7 @@
4143
throw new Error('unimplemented');
4244
} else {
4345
try {
44-
await user.login(username, passphrase);
46+
await user.login(username, passphrase, {remember_me});
4547
} catch(err) {
4648
page_error = err;
4749
}
@@ -136,31 +138,36 @@
136138
</div>
137139
{/if}
138140
<div class="mb-6" transition:slide="{{duration: 150}}">
139-
<Input bind:value={passphrase_confirm} type="password" name="confirm" label="{loc('passphrase_confirm')}" required=true tabindex=2 />
140-
<div class="my-2 p-4 bg-orange-100 dark:bg-orange-900/50">
141-
<Switch bind:checked={i_understand} name="understand" class="!mb-0" label="{loc('passphrase_understand')}" tabindex="3" supporting="{loc('passphrase_importance')}" />
141+
<Input bind:value={passphrase_confirm} type="password" name="confirm" label="{loc('passphrase_confirm')}" required=true tabindex=3 />
142+
<div class="my-2 p-4 bg-red-100 dark:bg-red-900/50">
143+
<Switch bind:checked={i_understand} name="understand" class="!mb-0" label="{loc('passphrase_understand')}" tabindex="4" supporting="{loc('passphrase_importance')}" />
142144
</div>
143145
</div>
144146
{/if}
147+
{#if app_config.user.enable_remember_me}
148+
<div class="mb-6 p-4 bg-orange-100 dark:bg-orange-900/50">
149+
<Switch bind:checked={remember_me} name="remember" class="!mb-0" label="{loc('stay_logged_in')}" tabindex=5 supporting="{loc('stay_logged_in_warning')}" />
150+
</div>
151+
{/if}
145152
<div class="flex items-center justify-center">
146153
{#if page_join_mode}
147-
<Button label="{loc('create_account')}" submit=true tabindex=4 />
148-
<Button on:click={open_login} label="{loc('login_existing')}" display="text" class="ml-2" tabindex=5 />
154+
<Button label="{loc('create_account')}" submit=true tabindex=6 />
155+
<Button on:click={open_login} label="{loc('login_existing')}" display="text" class="ml-2" tabindex=7 />
149156
{:else}
150-
<Button label="{loc('login')}" submit=true tabindex=4 />
151-
<Button on:click={open_join} label="{loc('create_account')}" display="text" class="ml-2" tabindex=5 />
157+
<Button label="{loc('login')}" submit=true tabindex=6 />
158+
<Button on:click={open_join} label="{loc('create_account')}" display="text" class="ml-2" tabindex=7 />
152159
{/if}
153160
</div>
154161

155162
<div class="flex items-center justify-center my-6 pt-6 border-t border-slate-200 dark:border-slate-600">
156-
<Button on:click={toggle_settings} label="{loc('advanced_settings')}" display="text" tabindex=6 />
163+
<Button on:click={toggle_settings} label="{loc('advanced_settings')}" display="text" tabindex=8 />
157164
</div>
158165

159166
{#if page_settings_open}
160167
<div transition:slide="{{duration: 150}}">
161-
<Input bind:value={turtl_server} type="text" name="server" label="{loc('turtl_server')}" tabindex=7 />
162-
<Input bind:value={proxy_server} type="text" name="proxy" label="{loc('proxy_server')}" tabindex=8 />
163-
<Switch bind:checked={skip_ssl_verification} name="skip_ssl" label="{loc('skip_ssl_verify')}" tabindex=9 />
168+
<Input bind:value={turtl_server} type="text" name="server" label="{loc('turtl_server')}" tabindex=9 />
169+
<Input bind:value={proxy_server} type="text" name="proxy" label="{loc('proxy_server')}" tabindex=10 />
170+
<Switch bind:checked={skip_ssl_verification} name="skip_ssl" label="{loc('skip_ssl_verify')}" tabindex=11 />
164171
</div>
165172
{/if}
166173
</form>

src/components/turtl/Space.svelte

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,15 @@
11
<script>
2+
import Button from '../form/Button.svelte';
23
import spaces from '@/models/turtl/spaces';
34
import user from '@/models/turtl/user';
5+
6+
$: space = spaces.get_active();
7+
8+
function set_active() {
9+
}
410
</script>
511

6-
<p class="dark:text-white">get a jobzzz</p>
12+
<p class="dark:text-white">{space.title}</p>
13+
14+
<Button on:click={set_active} label="CLICK ME!!" display="text" class="ml-2" tabindex=5 />
715

src/config.js

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
const config = {
2+
user: {
3+
// if true, shows "Stay logged in" options on login/join and enables the
4+
// functionality for saving logins in the rest of the app.
5+
enable_remember_me: true,
6+
},
27
log: {
38
default_level: 'warn',
49
},
5-
core: {
6-
// if true, we don't load or initialize the core at all, but rather
7-
// implement a number of mocked calls within the UI that allow for dev
8-
// without needing the core
9-
mock: true,
10-
},
1110
};
1211
export default config;
1312

src/locales/en_us.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ export const translation = {
2626
proxy_server: 'Proxy server',
2727
settings: 'Settings',
2828
skip_ssl_verify: 'Skip SSL verification',
29+
stay_logged_in: 'Stay logged in',
30+
stay_logged_in_warning: 'Staying logged in may compromise the security of your account.',
2931
too_short: 'Too short',
3032
turtl_server: 'Turtl server',
3133
weak: 'Weak',

src/models/remember-me.js

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import app_config from '@/config';
2+
import log from '@/util/log';
3+
import Emitter from '@/util/event';
4+
import user from './turtl/user';
5+
6+
let adapter = null;
7+
8+
function enabled() {
9+
return adapter && app_config.user.enable_remember_me;
10+
}
11+
12+
export async function init(remember_adapter, options) {
13+
adapter = remember_adapter;
14+
}
15+
16+
export async function save() {
17+
if(!enabled()) return;
18+
const {user_id, key} = await user.save_login();
19+
await adapter.save(user_id, key);
20+
}
21+
22+
export async function login() {
23+
if(!enabled()) return;
24+
const saved = await adapter.get();
25+
if(!saved) return;
26+
try {
27+
return user.login_from_saved(saved.user_id, saved.key);
28+
} catch(err) {
29+
log.notice('remember-me::login() -- Problem logging in from saved', err);
30+
}
31+
}
32+
33+
export async function clear() {
34+
if(!enabled()) return;
35+
await adapter.clear();
36+
}
37+

src/models/turtl/spaces.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,14 @@ import user from './user';
44
const spaces = writable([]);
55

66
spaces.get_active = () => {
7-
let space = get(spaces).find((s) => s.active === true);
7+
const got_spaces = get(spaces);
8+
let space = got_spaces.find((s) => s.active === true);
89
if(!space) {
910
const default_space = ((user.data() || {}).settings || {}).default_space;
10-
space = get(spaces).find((s) => s.id === default_space);
11+
space = got_spaces.find((s) => s.id === default_space);
12+
}
13+
if(!space) {
14+
space = got_spaces[0];
1115
}
1216
return space;
1317
};

0 commit comments

Comments
 (0)