Agree with terms
@@ -419,6 +618,27 @@ Terms.propTypes = {
onSubmit: PropTypes.func.isRequired,
};
+function EmailVerify({ email, onContinue }) {
+ return (
+
+
+
Verify email
+
+
+ {'Please check your email '}
+ {`(${email})`}
+ {' and validate before continuing further.'}
+
+
+
+
+
+ );
+}
+EmailVerify.propTypes = {
+ email: PropTypes.string.isRequired,
+};
+
function ProcessWrapper({ children }) {
return (
diff --git a/src/app/templates/auth/Auth.scss b/src/app/templates/auth/Auth.scss
index 678b90f1..ecc5011b 100644
--- a/src/app/templates/auth/Auth.scss
+++ b/src/app/templates/auth/Auth.scss
@@ -1,156 +1,144 @@
-.auth__wrapper {
+.auth__base {
+ --pattern-size: 48px;
min-height: 100vh;
- padding: var(--sp-loose);
background-color: var(--bg-surface-low);
- background-image: url("https://images.unsplash.com/photo-1562619371-b67725b6fde2?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=1950&q=80");
- background-size: cover;
- background-repeat: no-repeat;
- background-position: center;
+ background-image: radial-gradient(rgba(0, 0, 0, 6%) 2px, rgba(0, 0, 0, 0%) 2px);
+ background-size: var(--pattern-size) var(--pattern-size);
- .auth-card {
- width: 462px;
- min-height: 644px;
- background-color: var(--bg-surface-low);
- border-radius: var(--bo-radius);
- box-shadow: var(--bs-popup);
- overflow: hidden;
- display: flex;
- flex-flow: row nowrap;
-
- &__interactive{
- flex: 1;
- min-width: 0;
- }
+ display: flex;
+ flex-direction: column;
+}
+.auth__wrapper {
+ flex: 1;
+ padding: var(--sp-loose);
+ padding-bottom: 0;
+ display: flex;
+ justify-content: center;
+ align-items: flex-start;
+}
+.auth-footer {
+ padding: var(--sp-normal) 0;
+ display: flex;
+ justify-content: center;
+ align-items: center;
- &__interactive {
- padding: calc(var(--sp-normal) + var(--sp-extra-loose));
- padding-bottom: var(--sp-extra-loose);
- background-color: var(--bg-surface);
- }
+ & > *:nth-child(2n) {
+ margin: 0 var(--sp-loose);
+ }
+ & a {
+ color: var(--tc-surface-normal);
+ &:hover { text-decoration: underline; }
+ }
+}
+.auth-card {
+ width: 462px;
+ background-color: var(--bg-surface);
+ border-radius: var(--bo-radius);
+ box-shadow: var(--bs-popup);
+ overflow: hidden;
+ &__content {
+ padding: var(--sp-extra-loose) calc(var(--sp-normal) + var(--sp-extra-loose));
+ }
+ &__switch {
+ margin-top: var(--sp-loose) !important;
}
}
-.app-ident {
- margin-bottom: var(--sp-extra-loose);
-
- &__logo {
- width: 60px;
- height: 60px;
+.homeserver-form,
+.auth-form__heading {
+ & .context-menu .btn-surface .ic-raw {
+ width: 0;
}
- &__text {
- margin-left: calc(var(--sp-loose) + var(--sp-ultra-tight));
+}
- .text-s1 {
- margin-top: var(--sp-tight);
- color: var(--tc-surface-normal);
+.homeserver-form {
+ display: flex;
+ margin-bottom: var(--sp-extra-tight);
+ & > .input-container {
+ flex: 1;
+ & .input {
+ border-right: unset;
+ border-radius: var(--bo-radius) 0 0 var(--bo-radius);
+ background-color: var(--bg-surface);
}
+ }
+ & .ic-btn {
+ height: 46px;
+ align-self: flex-end;
+ border: 1px solid var(--bg-surface-border);
+ border-radius: 0 var(--bo-radius) var(--bo-radius) 0;
+ }
+ [dir=rtl] & {
+ & .input {
+ border-radius: 0 var(--bo-radius) var(--bo-radius) 0;
+ border-radius: 1px;
+ border-left: unset;
+ }
+ .ic-btn {
+ border-radius: var(--bo-radius) 0 0 var(--bo-radius);
+ }
+ }
- [dir=rtl] & {
- margin-left: 0;
- margin-right: calc(var(--sp-loose) + var(--sp-ultra-tight));
+ &__status {
+ margin-top: var(--sp-normal);
+ & .donut-spinner {
+ min-width: 28px;
}
+ & .text {
+ margin: 0 var(--sp-tight);
+ }
+ }
+ &__error {
+ margin-bottom: var(--sp-normal) !important;
+ color: var(--tc-danger-normal) !important;
}
}
.auth-form {
-
- & > .text {
- margin-bottom: var(--sp-loose);
- margin-top: var(--sp-loose);
- }
& > .input-container {
- margin-top: var(--sp-tight);
+ margin: var(--sp-tight) 0 var(--sp-ultra-tight);
+ }
+
+ &__heading {
+ display: flex;
+ justify-content: space-between;
+ margin-top: calc(var(--sp-extra-loose) + var(--sp-tight));
}
- .submit-btn__wrapper {
- margin-top: var(--sp-extra-loose);
- margin-bottom: var(--sp-loose);
- align-items: flex-start;
-
- & > .error-message {
- display: none;
- flex: 1;
- color: var(--tc-danger-normal);
- margin-right: var(--sp-normal);
- word-break: break;
-
- [dir=rtl] & {
- margin: {
- right: 0;
- left: var(--sp-normal);
- }
- }
- }
+ &__btns {
+ padding-top: var(--sp-loose);
+ margin-bottom: var(--sp-extra-loose);
+ display: flex;
+ justify-content: flex-end;
}
- &__wrapper {
- height: 100%;
+ &__error {
+ color: var(--tc-danger-normal) !important;
}
}
-
-.username__wrapper {
+.sso__divider {
+ margin-bottom: var(--sp-tight);
display: flex;
- align-items: flex-end;
+ align-items: center;
- & > :first-child {
+ &::before,
+ &::after {
flex: 1;
-
- .input {
- border-radius: var(--bo-radius) 0 0 var(--bo-radius);
-
- [dir=rtl] & {
- border-radius: 0 var(--bo-radius) var(--bo-radius) 0;
- }
- }
- }
- & > :last-child {
- width: 110px;
-
- .input {
- border-left-width: 0;
- background-color: var(--bg-surface);
- border-radius: 0 var(--bo-radius) var(--bo-radius) 0;
-
- [dir=rtl] & {
- border-left-width: 1px;
- border-right-width: 0;
- border-radius: var(--bo-radius) 0 0 var(--bo-radius);
- }
- }
- }
-}
-
-.password__wrapper {
- margin-top: var(--sp-tight);
- position: relative;
-
- & .ic-btn {
- position: absolute;
- right: 6px;
- bottom: 6px;
- border-radius: calc(var(--bo-radius) / 2);
- [dir=rtl] & {
- left: 6px;
- right: unset;
- }
+ content: '';
+ margin: var(--sp-tight);
+ border-bottom: 1px solid var(--bg-surface-border);
}
}
@media (max-width: 462px) {
.auth__wrapper {
- padding: 0;
- background-image: none;
- background-color: var(--bg-surface);
-
- .auth-card {
- border-radius: 0;
- box-shadow: none;
-
- &__interactive {
- padding: var(--sp-extra-loose);
- }
+ padding: var(--sp-tight);
+ }
+ .auth-card {
+ &__content {
+ padding: var(--sp-loose) var(--sp-normal);
}
}
}
diff --git a/src/client/action/auth.js b/src/client/action/auth.js
index 47fe2ba2..5631164c 100644
--- a/src/client/action/auth.js
+++ b/src/client/action/auth.js
@@ -1,189 +1,101 @@
import * as sdk from 'matrix-js-sdk';
import cons from '../state/cons';
-import { getBaseUrl } from '../../util/matrixUtil';
-// This method inspired by a similar one in matrix-react-sdk
-async function createTemporaryClient(homeserver) {
- let baseUrl = null;
- try {
- baseUrl = await getBaseUrl(homeserver);
- } catch (e) {
- baseUrl = `https://${homeserver}`;
- }
-
- if (typeof baseUrl === 'undefined') throw new Error('Homeserver not found');
+function updateLocalStore(accessToken, deviceId, userId, baseUrl) {
+ localStorage.setItem(cons.secretKey.ACCESS_TOKEN, accessToken);
+ localStorage.setItem(cons.secretKey.DEVICE_ID, deviceId);
+ localStorage.setItem(cons.secretKey.USER_ID, userId);
+ localStorage.setItem(cons.secretKey.BASE_URL, baseUrl);
+}
+function createTemporaryClient(baseUrl) {
return sdk.createClient({ baseUrl });
}
-async function getLoginFlows(client) {
- const flows = await client.loginFlows();
- if (flows !== undefined) {
- return flows;
- }
- return null;
-}
-
-async function startSsoLogin(homeserver, type, idpId) {
- const client = await createTemporaryClient(homeserver);
+async function startSsoLogin(baseUrl, type, idpId) {
+ const client = createTemporaryClient(baseUrl);
localStorage.setItem(cons.secretKey.BASE_URL, client.baseUrl);
window.location.href = client.getSsoLoginUrl(window.location.href, type, idpId);
}
-async function login(username, homeserver, password) {
- const client = await createTemporaryClient(homeserver);
+async function login(baseUrl, username, email, password) {
+ const identifier = {};
+ if (username) {
+ identifier.type = 'm.id.user';
+ identifier.user = username;
+ } else if (email) {
+ identifier.type = 'm.id.thirdparty';
+ identifier.medium = 'email';
+ identifier.address = email;
+ } else throw new Error('Bad Input');
- const response = await client.login('m.login.password', {
- identifier: {
- type: 'm.id.user',
- user: username,
- },
+ const client = createTemporaryClient(baseUrl);
+ const res = await client.login('m.login.password', {
+ identifier,
password,
initial_device_display_name: cons.DEVICE_DISPLAY_NAME,
});
- localStorage.setItem(cons.secretKey.ACCESS_TOKEN, response.access_token);
- localStorage.setItem(cons.secretKey.DEVICE_ID, response.device_id);
- localStorage.setItem(cons.secretKey.USER_ID, response.user_id);
- localStorage.setItem(cons.secretKey.BASE_URL, response?.well_known?.['m.homeserver']?.base_url || client.baseUrl);
+ const myBaseUrl = res?.well_known?.['m.homeserver']?.base_url || client.baseUrl;
+ updateLocalStore(res.access_token, res.device_id, res.user_id, myBaseUrl);
}
async function loginWithToken(baseUrl, token) {
- const client = sdk.createClient(baseUrl);
+ const client = createTemporaryClient(baseUrl);
- const response = await client.login('m.login.token', {
+ const res = await client.login('m.login.token', {
token,
initial_device_display_name: cons.DEVICE_DISPLAY_NAME,
});
- localStorage.setItem(cons.secretKey.ACCESS_TOKEN, response.access_token);
- localStorage.setItem(cons.secretKey.DEVICE_ID, response.device_id);
- localStorage.setItem(cons.secretKey.USER_ID, response.user_id);
- localStorage.setItem(cons.secretKey.BASE_URL, response?.well_known?.['m.homeserver']?.base_url || client.baseUrl);
+ const myBaseUrl = res?.well_known?.['m.homeserver']?.base_url || client.baseUrl;
+ updateLocalStore(res.access_token, res.device_id, res.user_id, myBaseUrl);
}
-async function getAdditionalInfo(baseUrl, content) {
- try {
- const res = await fetch(`${baseUrl}/_matrix/client/r0/register`, {
- method: 'POST',
- body: JSON.stringify(content),
- headers: {
- 'Content-Type': 'application/json; charset=utf-8',
- },
- credentials: 'same-origin',
- });
- const data = await res.json();
- return data;
- } catch (e) {
- throw new Error(e);
- }
-}
-
-async function verifyEmail(baseUrl, content) {
- try {
- const res = await fetch(`${baseUrl}/_matrix/client/r0/register/email/requestToken`, {
- method: 'POST',
- body: JSON.stringify(content),
- headers: {
- 'Content-Type': 'application/json; charset=utf-8',
- },
- credentials: 'same-origin',
- });
- const data = await res.json();
- return data;
- } catch (e) {
- throw new Error(e);
- }
-}
-
-let session = null;
-let clientSecret = null;
-let sid = null;
-async function register(username, homeserver, password, email, recaptchaValue, terms, verified) {
- const baseUrl = await getBaseUrl(homeserver);
-
- if (typeof baseUrl === 'undefined') throw new Error('Homeserver not found');
-
- const client = sdk.createClient({ baseUrl });
-
- const isAvailable = await client.isUsernameAvailable(username);
- if (!isAvailable) throw new Error('Username not available');
-
- if (typeof recaptchaValue === 'string') {
- await getAdditionalInfo(baseUrl, {
- auth: {
- type: 'm.login.recaptcha',
- session,
- response: recaptchaValue,
- },
- });
- } else if (terms === true) {
- await getAdditionalInfo(baseUrl, {
- auth: {
- type: 'm.login.terms',
- session,
- },
- });
- } else if (verified !== true) {
- session = null;
- clientSecret = client.generateClientSecret();
- const verifyData = await verifyEmail(baseUrl, {
- email,
- client_secret: clientSecret,
- send_attempt: 1,
- });
- if (typeof verifyData.error === 'string') {
- throw new Error(verifyData.error);
- }
- sid = verifyData.sid;
- }
-
- const additionalInfo = await getAdditionalInfo(baseUrl, {
- auth: { session: (session !== null) ? session : undefined },
+// eslint-disable-next-line camelcase
+async function verifyEmail(baseUrl, email, client_secret, send_attempt, next_link) {
+ const res = await fetch(`${baseUrl}/_matrix/client/r0/register/email/requestToken`, {
+ method: 'POST',
+ body: JSON.stringify({
+ email, client_secret, send_attempt, next_link,
+ }),
+ headers: {
+ 'Content-Type': 'application/json; charset=utf-8',
+ },
+ credentials: 'same-origin',
});
- session = additionalInfo.session;
- if (typeof additionalInfo.completed === 'undefined' || additionalInfo.completed.length === 0) {
- return ({
- type: 'recaptcha',
- public_key: additionalInfo.params['m.login.recaptcha'].public_key,
- });
- }
- if (additionalInfo.completed.find((process) => process === 'm.login.recaptcha') === 'm.login.recaptcha'
- && !additionalInfo.completed.find((process) => process === 'm.login.terms')) {
- return ({
- type: 'terms',
- en: additionalInfo.params['m.login.terms'].policies.privacy_policy.en,
- });
- }
- if (verified || additionalInfo.completed.find((process) => process === 'm.login.terms') === 'm.login.terms') {
- const tpc = {
- client_secret: clientSecret,
- sid,
- };
- const verifyData = await getAdditionalInfo(baseUrl, {
- auth: {
- session,
- type: 'm.login.email.identity',
- threepidCreds: tpc,
- threepid_creds: tpc,
- },
- username,
- password,
- });
- if (verifyData.errcode === 'M_UNAUTHORIZED') {
- return { type: 'email' };
- }
+ const data = await res.json();
+ return data;
+}
- localStorage.setItem(cons.secretKey.ACCESS_TOKEN, verifyData.access_token);
- localStorage.setItem(cons.secretKey.DEVICE_ID, verifyData.device_id);
- localStorage.setItem(cons.secretKey.USER_ID, verifyData.user_id);
- localStorage.setItem(cons.secretKey.BASE_URL, baseUrl);
- return { type: 'done' };
+async function completeRegisterStage(
+ baseUrl, username, password, auth,
+) {
+ const tempClient = createTemporaryClient(baseUrl);
+
+ try {
+ const result = await tempClient.registerRequest({
+ username, password, auth,
+ });
+ const data = { completed: result.completed || [] };
+ if (result.access_token) {
+ data.done = true;
+ updateLocalStore(result.access_token, result.device_id, result.user_id, baseUrl);
+ }
+ return data;
+ } catch (e) {
+ const result = e.data;
+ const data = { completed: result.completed || [] };
+ if (result.access_token) {
+ data.done = true;
+ updateLocalStore(result.access_token, result.device_id, result.user_id, baseUrl);
+ }
+ return data;
}
- return {};
}
export {
- createTemporaryClient, getLoginFlows, login,
- loginWithToken, register, startSsoLogin,
+ createTemporaryClient, login, verifyEmail,
+ loginWithToken, startSsoLogin,
+ completeRegisterStage,
};
diff --git a/src/client/state/cons.js b/src/client/state/cons.js
index 6cd177e2..f554c9a8 100644
--- a/src/client/state/cons.js
+++ b/src/client/state/cons.js
@@ -1,4 +1,5 @@
const cons = {
+ version: '1.4.0',
secretKey: {
ACCESS_TOKEN: 'cinny_access_token',
DEVICE_ID: 'cinny_device_id',
diff --git a/src/util/matrixUtil.js b/src/util/matrixUtil.js
index e40fa73c..e0b07100 100644
--- a/src/util/matrixUtil.js
+++ b/src/util/matrixUtil.js
@@ -2,15 +2,18 @@ import initMatrix from '../client/initMatrix';
const WELL_KNOWN_URI = '/.well-known/matrix/client';
-async function getBaseUrl(homeserver) {
- const serverDiscoveryUrl = `https://${homeserver}${WELL_KNOWN_URI}`;
+async function getBaseUrl(servername) {
+ let protocol = 'https://';
+ if (servername.match(/^https?:\/\//) !== null) protocol = '';
+ const serverDiscoveryUrl = `${protocol}${servername}${WELL_KNOWN_URI}`;
try {
- const result = await fetch(serverDiscoveryUrl, { method: 'GET' });
- const data = await result.json();
+ const result = await (await fetch(serverDiscoveryUrl, { method: 'GET' })).json();
- return data?.['m.homeserver']?.base_url;
+ const baseUrl = result?.['m.homeserver']?.base_url;
+ if (baseUrl === undefined) throw new Error();
+ return baseUrl;
} catch (e) {
- throw new Error('Homeserver not found');
+ throw new Error(`${protocol}${servername}`);
}
}