kempo 4.4.4 → 4.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/app-public/login/index.page.html +4 -38
- package/app-public/register/index.page.html +4 -47
- package/dist/kempo/components/Login.js +18 -0
- package/dist/kempo/components/Login.js.map +1 -0
- package/dist/kempo/components/Register.js +21 -0
- package/dist/kempo/components/Register.js.map +1 -0
- package/dist/kempo/icons/login.svg +1 -0
- package/dist/kempo/utils/safeNext.js +2 -0
- package/dist/kempo/utils/safeNext.js.map +1 -0
- package/package.json +1 -1
|
@@ -7,45 +7,11 @@
|
|
|
7
7
|
<content>
|
|
8
8
|
<main class="p">
|
|
9
9
|
<div style="max-width: 400px; margin: 0 auto;">
|
|
10
|
-
<
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
<label for="email" class="d-b mb-sm">Email</label>
|
|
14
|
-
<input type="email" id="email" name="email" required class="w-full p-sm br">
|
|
15
|
-
</div>
|
|
16
|
-
<div class="mb">
|
|
17
|
-
<label for="password" class="d-b mb-sm">Password</label>
|
|
18
|
-
<input type="password" id="password" name="password" required class="w-full p-sm br">
|
|
19
|
-
</div>
|
|
20
|
-
<button type="submit" class="w-full p-sm br">Login</button>
|
|
21
|
-
</form>
|
|
22
|
-
<p class="mt">
|
|
23
|
-
<a href="{{pathToRoot}}forgot-password">Forgot Password?</a>
|
|
24
|
-
</p>
|
|
25
|
-
<p class="mt">Don't have an account? <a href="{{pathToRoot}}register">Register</a></p>
|
|
10
|
+
<k-login redirect="{{pathToRoot}}account">
|
|
11
|
+
<location name="login-form-after" label="After the Login Form" />
|
|
12
|
+
</k-login>
|
|
26
13
|
</div>
|
|
27
14
|
</main>
|
|
28
|
-
|
|
29
|
-
<content location="scripts">
|
|
30
|
-
<script type="module">
|
|
31
|
-
import { login } from '{{pathToRoot}}kempo/sdk.js';
|
|
32
|
-
import Toast from '{{pathToRoot}}kempo-ui/components/Toast.js';
|
|
33
|
-
|
|
34
|
-
const form = document.getElementById('loginForm');
|
|
35
|
-
|
|
36
|
-
form.addEventListener('submit', async (e) => {
|
|
37
|
-
e.preventDefault();
|
|
38
|
-
|
|
39
|
-
const email = document.getElementById('email').value;
|
|
40
|
-
const password = document.getElementById('password').value;
|
|
41
|
-
|
|
42
|
-
const [error] = await login({ email, password });
|
|
43
|
-
if(error){
|
|
44
|
-
Toast.error(error.msg);
|
|
45
|
-
return;
|
|
46
|
-
}
|
|
47
|
-
window.location.href = '{{pathToRoot}}account';
|
|
48
|
-
});
|
|
49
|
-
</script>
|
|
15
|
+
<script type="module" src="{{pathToRoot}}kempo/components/Login.js"></script>
|
|
50
16
|
</content>
|
|
51
17
|
</page>
|
|
@@ -7,54 +7,11 @@
|
|
|
7
7
|
<content>
|
|
8
8
|
<main class="p">
|
|
9
9
|
<div style="max-width: 400px; margin: 0 auto;">
|
|
10
|
-
<
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
<label for="name" class="d-b mb-sm">Name</label>
|
|
14
|
-
<input type="text" id="name" name="name" required class="w-full p-sm br">
|
|
15
|
-
</div>
|
|
16
|
-
<div class="mb">
|
|
17
|
-
<label for="email" class="d-b mb-sm">Email</label>
|
|
18
|
-
<input type="email" id="email" name="email" required class="w-full p-sm br">
|
|
19
|
-
</div>
|
|
20
|
-
<div class="mb">
|
|
21
|
-
<label for="password" class="d-b mb-sm">Password</label>
|
|
22
|
-
<input type="password" id="password" name="password" required class="w-full p-sm br">
|
|
23
|
-
</div>
|
|
24
|
-
<div id="error" class="mb c-error" style="display: none;"></div>
|
|
25
|
-
<button type="submit" class="w-full p-sm br">Register</button>
|
|
26
|
-
</form>
|
|
27
|
-
<p class="mt">Already have an account? <a href="{{pathToRoot}}login">Login</a></p>
|
|
10
|
+
<k-register redirect="{{pathToRoot}}account" verify-redirect="{{pathToRoot}}verify-email">
|
|
11
|
+
<location name="register-form-after" label="After the Register Form" />
|
|
12
|
+
</k-register>
|
|
28
13
|
</div>
|
|
29
14
|
</main>
|
|
30
|
-
|
|
31
|
-
<content location="scripts">
|
|
32
|
-
<script type="module">
|
|
33
|
-
import { register } from '{{pathToRoot}}kempo/sdk.js';
|
|
34
|
-
import Toast from '{{pathToRoot}}kempo-ui/components/Toast.js';
|
|
35
|
-
|
|
36
|
-
const form = document.getElementById('registerForm');
|
|
37
|
-
const errorDiv = document.getElementById('error');
|
|
38
|
-
|
|
39
|
-
form.addEventListener('submit', async (e) => {
|
|
40
|
-
e.preventDefault();
|
|
41
|
-
errorDiv.style.display = 'none';
|
|
42
|
-
|
|
43
|
-
const name = document.getElementById('name').value;
|
|
44
|
-
const email = document.getElementById('email').value;
|
|
45
|
-
const password = document.getElementById('password').value;
|
|
46
|
-
|
|
47
|
-
const [error, result] = await register({ name, email, password });
|
|
48
|
-
if(error){
|
|
49
|
-
Toast.error(error.msg);
|
|
50
|
-
return;
|
|
51
|
-
}
|
|
52
|
-
if(result.requiresVerification){
|
|
53
|
-
window.location.href = '{{pathToRoot}}verify-email';
|
|
54
|
-
} else {
|
|
55
|
-
window.location.href = '{{pathToRoot}}account';
|
|
56
|
-
}
|
|
57
|
-
});
|
|
58
|
-
</script>
|
|
15
|
+
<script type="module" src="{{pathToRoot}}kempo/components/Register.js"></script>
|
|
59
16
|
</content>
|
|
60
17
|
</page>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import e from"/kempo-ui/components/LightComponent.js";import{html as s}from"/kempo-ui/lit-all.min.js";import t from"/kempo-ui/components/Toast.js";import{login as r}from"../sdk.js";import o from"../utils/safeNext.js";export default class a extends e{static properties={redirect:{type:String}};constructor(){super(),this.redirect="/account",this.extra=[]}connectedCallback(){this.extra.length||(this.extra=[...this.childNodes]),this.extra.forEach(e=>e.remove()),super.connectedCallback()}submit=async e=>{e.preventDefault();const s=new FormData(e.target),[a]=await r({email:s.get("email"),password:s.get("password")});a?t.error(a.msg):window.location.href=o(new URLSearchParams(window.location.search).get("next"))||this.redirect};renderLightDom(){return s`
|
|
2
|
+
<h2>Login</h2>
|
|
3
|
+
<form @submit=${this.submit}>
|
|
4
|
+
<div class="mb">
|
|
5
|
+
<label for="k-login-email" class="d-b mb-sm">Email</label>
|
|
6
|
+
<input type="email" id="k-login-email" name="email" required class="w-full p-sm br">
|
|
7
|
+
</div>
|
|
8
|
+
<div class="mb">
|
|
9
|
+
<label for="k-login-password" class="d-b mb-sm">Password</label>
|
|
10
|
+
<input type="password" id="k-login-password" name="password" required class="w-full p-sm br">
|
|
11
|
+
</div>
|
|
12
|
+
<button type="submit" class="w-full p-sm br">Login</button>
|
|
13
|
+
</form>
|
|
14
|
+
${this.extra}
|
|
15
|
+
<p class="mt"><a href="/forgot-password">Forgot Password?</a></p>
|
|
16
|
+
<p class="mt">Don't have an account? <a href="/register">Register</a></p>
|
|
17
|
+
`}}customElements.define("k-login",a);
|
|
18
|
+
//# sourceMappingURL=c:\Users\dusti\dev\kempo\dist\kempo\components\Login.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"src\\kempo\\components\\Login.js","names":["LightComponent","html","Toast","login","safeNext","Login","static","redirect","type","String","constructor","super","this","extra","connectedCallback","length","childNodes","forEach","node","remove","submit","async","event","preventDefault","form","FormData","target","error","email","get","password","msg","window","location","href","URLSearchParams","search","renderLightDom","customElements","define"],"sources":["0"],"sourcesContent":["import LightComponent from '/kempo-ui/components/LightComponent.js';\nimport { html } from '/kempo-ui/lit-all.min.js';\nimport Toast from '/kempo-ui/components/Toast.js';\nimport { login } from '../sdk.js';\nimport safeNext from '../utils/safeNext.js';\n\n/*\n The login form.\n\n Lives in kempo, not in each site's copy of the login page, so an update to kempo reaches every\n site. A site's page is just this element, and anything it needs to differ is an attribute:\n\n - `redirect`: where to go after signing in (default `/account`). A same-site `?next=` in the\n page's URL wins over it.\n\n **Content placed inside the element is kept** and shown between the form and the links. That is\n how an extension adds another way in: the page holds `<location name=\"login-form-after\" />`\n inside `<k-login>`, the server fills it before the browser sees the page, and it ends up here.\n*/\nexport default class Login extends LightComponent {\n static properties = {\n redirect: { type: String },\n };\n\n constructor(){\n super();\n this.redirect = '/account';\n this.extra = [];\n }\n\n /*\n Lifecycle\n */\n\n connectedCallback(){\n // Taken before the first render, which would otherwise leave them sitting after it.\n if(!this.extra.length) this.extra = [...this.childNodes];\n this.extra.forEach(node => node.remove());\n super.connectedCallback();\n }\n\n /*\n Events\n */\n\n submit = async event => {\n event.preventDefault();\n const form = new FormData(event.target);\n\n const [error] = await login({ email: form.get('email'), password: form.get('password') });\n if(error){\n Toast.error(error.msg);\n return;\n }\n\n window.location.href = safeNext(new URLSearchParams(window.location.search).get('next')) || this.redirect;\n };\n\n /*\n Rendering\n */\n\n renderLightDom(){\n return html`\n <h2>Login</h2>\n <form @submit=${this.submit}>\n <div class=\"mb\">\n <label for=\"k-login-email\" class=\"d-b mb-sm\">Email</label>\n <input type=\"email\" id=\"k-login-email\" name=\"email\" required class=\"w-full p-sm br\">\n </div>\n <div class=\"mb\">\n <label for=\"k-login-password\" class=\"d-b mb-sm\">Password</label>\n <input type=\"password\" id=\"k-login-password\" name=\"password\" required class=\"w-full p-sm br\">\n </div>\n <button type=\"submit\" class=\"w-full p-sm br\">Login</button>\n </form>\n ${this.extra}\n <p class=\"mt\"><a href=\"/forgot-password\">Forgot Password?</a></p>\n <p class=\"mt\">Don't have an account? <a href=\"/register\">Register</a></p>\n `;\n }\n}\n\ncustomElements.define('k-login', Login);\n"],"mappings":"OAAOA,MAAoB,wDAClBC,MAAY,kCACdC,MAAW,gDACTC,MAAa,mBACfC,MAAc,sCAeN,MAAMC,UAAcL,EACjCM,kBAAoB,CAClBC,SAAU,CAAEC,KAAMC,SAGpB,WAAAC,GACEC,QACAC,KAAKL,SAAW,WAChBK,KAAKC,MAAQ,EACf,CAMA,iBAAAC,GAEMF,KAAKC,MAAME,SAAQH,KAAKC,MAAQ,IAAID,KAAKI,aAC7CJ,KAAKC,MAAMI,QAAQC,GAAQA,EAAKC,UAChCR,MAAMG,mBACR,CAMAM,OAASC,UACPC,EAAMC,iBACN,MAAMC,EAAO,IAAIC,SAASH,EAAMI,SAEzBC,SAAexB,EAAM,CAAEyB,MAAOJ,EAAKK,IAAI,SAAUC,SAAUN,EAAKK,IAAI,cACxEF,EACDzB,EAAMyB,MAAMA,EAAMI,KAIpBC,OAAOC,SAASC,KAAO9B,EAAS,IAAI+B,gBAAgBH,OAAOC,SAASG,QAAQP,IAAI,UAAYjB,KAAKL,UAOnG,cAAA8B,GACE,OAAOpC,CAAI;;sBAEOW,KAAKQ;;;;;;;;;;;QAWnBR,KAAKC;;;KAIX,EAGFyB,eAAeC,OAAO,UAAWlC","ignoreList":[]}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import e from"/kempo-ui/components/LightComponent.js";import{html as t}from"/kempo-ui/lit-all.min.js";import r from"/kempo-ui/components/Toast.js";import{register as s}from"../sdk.js";import i from"../utils/safeNext.js";export default class a extends e{static properties={redirect:{type:String},verifyRedirect:{type:String,attribute:"verify-redirect"}};constructor(){super(),this.redirect="/account",this.verifyRedirect="/verify-email",this.extra=[]}connectedCallback(){this.extra.length||(this.extra=[...this.childNodes]),this.extra.forEach(e=>e.remove()),super.connectedCallback()}submit=async e=>{e.preventDefault();const t=new FormData(e.target),[a,m]=await s({name:t.get("name"),email:t.get("email"),password:t.get("password")});a?r.error(a.msg):window.location.href=m.requiresVerification?this.verifyRedirect:i(new URLSearchParams(window.location.search).get("next"))||this.redirect};renderLightDom(){return t`
|
|
2
|
+
<h2>Register</h2>
|
|
3
|
+
<form @submit=${this.submit}>
|
|
4
|
+
<div class="mb">
|
|
5
|
+
<label for="k-register-name" class="d-b mb-sm">Name</label>
|
|
6
|
+
<input type="text" id="k-register-name" name="name" required class="w-full p-sm br">
|
|
7
|
+
</div>
|
|
8
|
+
<div class="mb">
|
|
9
|
+
<label for="k-register-email" class="d-b mb-sm">Email</label>
|
|
10
|
+
<input type="email" id="k-register-email" name="email" required class="w-full p-sm br">
|
|
11
|
+
</div>
|
|
12
|
+
<div class="mb">
|
|
13
|
+
<label for="k-register-password" class="d-b mb-sm">Password</label>
|
|
14
|
+
<input type="password" id="k-register-password" name="password" required class="w-full p-sm br">
|
|
15
|
+
</div>
|
|
16
|
+
<button type="submit" class="w-full p-sm br">Register</button>
|
|
17
|
+
</form>
|
|
18
|
+
${this.extra}
|
|
19
|
+
<p class="mt">Already have an account? <a href="/login">Login</a></p>
|
|
20
|
+
`}}customElements.define("k-register",a);
|
|
21
|
+
//# sourceMappingURL=c:\Users\dusti\dev\kempo\dist\kempo\components\Register.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"src\\kempo\\components\\Register.js","names":["LightComponent","html","Toast","register","safeNext","Register","static","redirect","type","String","verifyRedirect","attribute","constructor","super","this","extra","connectedCallback","length","childNodes","forEach","node","remove","submit","async","event","preventDefault","form","FormData","target","error","result","name","get","email","password","msg","window","location","href","requiresVerification","URLSearchParams","search","renderLightDom","customElements","define"],"sources":["0"],"sourcesContent":["import LightComponent from '/kempo-ui/components/LightComponent.js';\nimport { html } from '/kempo-ui/lit-all.min.js';\nimport Toast from '/kempo-ui/components/Toast.js';\nimport { register } from '../sdk.js';\nimport safeNext from '../utils/safeNext.js';\n\n/*\n The registration form. See Login.js for why this is a component and how content placed inside it\n is kept: the page holds `<location name=\"register-form-after\" />` inside `<k-register>`.\n\n - `redirect`: where to go after registering (default `/account`). A same-site `?next=` wins.\n - `verify-redirect`: where to go instead when the site requires a verified email (default\n `/verify-email`).\n*/\nexport default class Register extends LightComponent {\n static properties = {\n redirect: { type: String },\n verifyRedirect: { type: String, attribute: 'verify-redirect' },\n };\n\n constructor(){\n super();\n this.redirect = '/account';\n this.verifyRedirect = '/verify-email';\n this.extra = [];\n }\n\n /*\n Lifecycle\n */\n\n connectedCallback(){\n if(!this.extra.length) this.extra = [...this.childNodes];\n this.extra.forEach(node => node.remove());\n super.connectedCallback();\n }\n\n /*\n Events\n */\n\n submit = async event => {\n event.preventDefault();\n const form = new FormData(event.target);\n\n const [error, result] = await register({ name: form.get('name'), email: form.get('email'), password: form.get('password') });\n if(error){\n Toast.error(error.msg);\n return;\n }\n\n window.location.href = result.requiresVerification\n ? this.verifyRedirect\n : safeNext(new URLSearchParams(window.location.search).get('next')) || this.redirect;\n };\n\n /*\n Rendering\n */\n\n renderLightDom(){\n return html`\n <h2>Register</h2>\n <form @submit=${this.submit}>\n <div class=\"mb\">\n <label for=\"k-register-name\" class=\"d-b mb-sm\">Name</label>\n <input type=\"text\" id=\"k-register-name\" name=\"name\" required class=\"w-full p-sm br\">\n </div>\n <div class=\"mb\">\n <label for=\"k-register-email\" class=\"d-b mb-sm\">Email</label>\n <input type=\"email\" id=\"k-register-email\" name=\"email\" required class=\"w-full p-sm br\">\n </div>\n <div class=\"mb\">\n <label for=\"k-register-password\" class=\"d-b mb-sm\">Password</label>\n <input type=\"password\" id=\"k-register-password\" name=\"password\" required class=\"w-full p-sm br\">\n </div>\n <button type=\"submit\" class=\"w-full p-sm br\">Register</button>\n </form>\n ${this.extra}\n <p class=\"mt\">Already have an account? <a href=\"/login\">Login</a></p>\n `;\n }\n}\n\ncustomElements.define('k-register', Register);\n"],"mappings":"OAAOA,MAAoB,wDAClBC,MAAY,kCACdC,MAAW,mDACTC,MAAgB,mBAClBC,MAAc,sCAUN,MAAMC,UAAiBL,EACpCM,kBAAoB,CAClBC,SAAU,CAAEC,KAAMC,QAClBC,eAAgB,CAAEF,KAAMC,OAAQE,UAAW,oBAG7C,WAAAC,GACEC,QACAC,KAAKP,SAAW,WAChBO,KAAKJ,eAAiB,gBACtBI,KAAKC,MAAQ,EACf,CAMA,iBAAAC,GACMF,KAAKC,MAAME,SAAQH,KAAKC,MAAQ,IAAID,KAAKI,aAC7CJ,KAAKC,MAAMI,QAAQC,GAAQA,EAAKC,UAChCR,MAAMG,mBACR,CAMAM,OAASC,UACPC,EAAMC,iBACN,MAAMC,EAAO,IAAIC,SAASH,EAAMI,SAEzBC,EAAOC,SAAgB3B,EAAS,CAAE4B,KAAML,EAAKM,IAAI,QAASC,MAAOP,EAAKM,IAAI,SAAUE,SAAUR,EAAKM,IAAI,cAC3GH,EACD3B,EAAM2B,MAAMA,EAAMM,KAIpBC,OAAOC,SAASC,KAAOR,EAAOS,qBAC1BzB,KAAKJ,eACLN,EAAS,IAAIoC,gBAAgBJ,OAAOC,SAASI,QAAQT,IAAI,UAAYlB,KAAKP,UAOhF,cAAAmC,GACE,OAAOzC,CAAI;;sBAEOa,KAAKQ;;;;;;;;;;;;;;;QAenBR,KAAKC;;KAGX,EAGF4B,eAAeC,OAAO,aAAcvC","ignoreList":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path fill="currentColor" d="M480-120v-80h280v-560H480v-80h280q33 0 56.5 23.5T840-760v560q0 33-23.5 56.5T760-120H480Zm-80-160-55-58 102-102H120v-80h327L345-622l55-58 200 200-200 200Z"/></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"src\\kempo\\utils\\safeNext.js","names":["value","length","startsWith","test"],"sources":["0"],"sourcesContent":["/*\n A path on this site and nothing else, or an empty string. A `?next=` is attacker-controlled, and a\n login page is the worst place for an open redirect: the link is trusted right up until the\n password is typed. Refused: anything not starting with a single `/` (so no `https://…` and no\n scheme-relative `//host`), backslashes (browsers read `/\\host` as `//host`), and control characters.\n*/\nexport default value => {\n if(typeof value !== 'string' || value.length > 2048) return '';\n if(!value.startsWith('/') || value.startsWith('//')) return '';\n if(/[\\\\\\u0000-\\u001f\\u007f]/.test(value)) return '';\n return value;\n};\n"],"mappings":"eAMeA,GACO,iBAAVA,GAAsBA,EAAMC,OAAS,OAC3CD,EAAME,WAAW,MAAQF,EAAME,WAAW,OAC3C,0BAA0BC,KAAKH,GAF0B,GAGrDA","ignoreList":[]}
|