Merge branch '434-support-captcha-if-required-in-registration-process' into 'dev'
Support authentication flows for login/register See merge request keanuapp/keanuapp-weblite!159
This commit is contained in:
commit
68c21a36b1
11 changed files with 481 additions and 139 deletions
|
|
@ -63,7 +63,7 @@ export default {
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.log("Error creating client", error);
|
console.log("Error creating client", error);
|
||||||
if (error.data.errcode ==='M_FORBIDDEN' && this.currentUser.is_guest) {
|
if (error.data && ((error.data.errcode ==='M_FORBIDDEN' && this.currentUser.is_guest) || error.data.errcode ==='M_USER_DEACTIVATED')) {
|
||||||
// Guest account and password don't work. We are in a strange state, probably because
|
// Guest account and password don't work. We are in a strange state, probably because
|
||||||
// of server cleanup of accounts or similar. Wipe account and restart...
|
// of server cleanup of accounts or similar. Wipe account and restart...
|
||||||
this.$store.commit("setUser", null);
|
this.$store.commit("setUser", null);
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@
|
||||||
"languageSupportEmail": "support@guardianproject.info",
|
"languageSupportEmail": "support@guardianproject.info",
|
||||||
"productLink": "letsconvene.im",
|
"productLink": "letsconvene.im",
|
||||||
"defaultServer": "https://neo.keanu.im",
|
"defaultServer": "https://neo.keanu.im",
|
||||||
|
"identityServer_unset": "",
|
||||||
"rtl": false,
|
"rtl": false,
|
||||||
"analytics": [
|
"analytics": [
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -148,7 +148,15 @@
|
||||||
"login": "Login",
|
"login": "Login",
|
||||||
"create_room": "Register & Create Room",
|
"create_room": "Register & Create Room",
|
||||||
"or": "OR",
|
"or": "OR",
|
||||||
"invalid_message": "Invalid username or password"
|
"invalid_message": "Invalid username or password",
|
||||||
|
"no_supported_flow": "The app can't login to the given server",
|
||||||
|
"terms": "The homeserver requires you to review and accept the following policies:",
|
||||||
|
"accept_terms": "Accept",
|
||||||
|
"email": "You need to verify you email address",
|
||||||
|
"send_verification": "Send verification email",
|
||||||
|
"sent_verification": "An email has been sent to {email}. Please use your regular email client to verify the address.",
|
||||||
|
"resend_verification": "Resend verification email",
|
||||||
|
"email_not_valid": "Email address not valid"
|
||||||
},
|
},
|
||||||
"profile": {
|
"profile": {
|
||||||
"title": "My Profile",
|
"title": "My Profile",
|
||||||
|
|
|
||||||
|
|
@ -322,6 +322,7 @@ export default {
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
waitingForRoomObject: false,
|
||||||
events: [],
|
events: [],
|
||||||
currentInput: "",
|
currentInput: "",
|
||||||
typingMembers: [],
|
typingMembers: [],
|
||||||
|
|
@ -419,7 +420,6 @@ export default {
|
||||||
computed: {
|
computed: {
|
||||||
chatContainer() {
|
chatContainer() {
|
||||||
const container = this.$refs.chatContainer;
|
const container = this.$refs.chatContainer;
|
||||||
console.log("GOT CONTAINER", container);
|
|
||||||
if (this.useVoiceMode) {
|
if (this.useVoiceMode) {
|
||||||
return container.$el;
|
return container.$el;
|
||||||
}
|
}
|
||||||
|
|
@ -547,6 +547,7 @@ export default {
|
||||||
this.$matrix.off("Room.timeline", this.onEvent);
|
this.$matrix.off("Room.timeline", this.onEvent);
|
||||||
this.$matrix.off("RoomMember.typing", this.onUserTyping);
|
this.$matrix.off("RoomMember.typing", this.onUserTyping);
|
||||||
|
|
||||||
|
this.waitingForRoomObject = false;
|
||||||
this.events = [];
|
this.events = [];
|
||||||
this.timelineWindow = null;
|
this.timelineWindow = null;
|
||||||
this.typingMembers = [];
|
this.typingMembers = [];
|
||||||
|
|
@ -557,27 +558,32 @@ export default {
|
||||||
this.stopRRTimer();
|
this.stopRRTimer();
|
||||||
this.lastRR = null;
|
this.lastRR = null;
|
||||||
|
|
||||||
if (!this.room) {
|
if (this.roomId) {
|
||||||
// Public room?
|
this.$matrix.isJoinedToRoom(this.roomId).then(joined => {
|
||||||
if (this.roomId && this.roomId.startsWith("#")) {
|
if (!joined) {
|
||||||
this.onRoomNotJoined();
|
this.onRoomNotJoined();
|
||||||
} else if (this.roomId) {
|
} else {
|
||||||
this.onRoomNotJoined(); // Private room we are not joined to. What to do? We redirect to join
|
if (this.room) {
|
||||||
// screen, maybe the user has an invite already?
|
this.onRoomJoined(this.readMarker);
|
||||||
|
} else {
|
||||||
|
this.waitingForRoomObject = true;
|
||||||
|
return; // no room, wait for it (we know we are joined so need to wait for sync to complete)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
this.initialLoadDone = true;
|
this.initialLoadDone = true;
|
||||||
return; // no room
|
return; // no room
|
||||||
}
|
}
|
||||||
|
|
||||||
// Joined?
|
|
||||||
if (this.room.hasMembershipState(this.currentUser.user_id, "join")) {
|
|
||||||
// Yes, load everything
|
|
||||||
this.onRoomJoined(this.readMarker);
|
|
||||||
} else {
|
|
||||||
this.onRoomNotJoined();
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
room() {
|
||||||
|
// Were we waiting?
|
||||||
|
if (this.room && this.room.roomId == this.roomId && this.waitingForRoomObject) {
|
||||||
|
this.waitingForRoomObject = false;
|
||||||
|
this.onRoomJoined(this.readMarker);
|
||||||
|
}
|
||||||
|
},
|
||||||
showMessageOperations() {
|
showMessageOperations() {
|
||||||
if (this.showMessageOperations) {
|
if (this.showMessageOperations) {
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
|
|
|
||||||
|
|
@ -39,8 +39,8 @@
|
||||||
background-color="white" v-on:keyup.enter="$refs.topic.focus()" :disabled="step > steps.INITIAL" autofocus
|
background-color="white" v-on:keyup.enter="$refs.topic.focus()" :disabled="step > steps.INITIAL" autofocus
|
||||||
solo @update:error="updateErrorState"></v-text-field>
|
solo @update:error="updateErrorState"></v-text-field>
|
||||||
<div class="text-left font-weight-light" v-show="roomName.length > 0">{{ $t("new_room.room_topic") }}</div>
|
<div class="text-left font-weight-light" v-show="roomName.length > 0">{{ $t("new_room.room_topic") }}</div>
|
||||||
<v-text-field v-model="roomTopic" v-show="roomName.length > 0" color="black" background-color="white"
|
<v-text-field v-model="roomTopic" v-show="roomName.length > 0" ref="topic" color="black" background-color="white"
|
||||||
v-on:keyup.enter="$refs.create.focus()" :disabled="step > steps.INITIAL" solo></v-text-field>
|
v-on:keyup.enter="$refs.create.$el.focus()" :disabled="step > steps.INITIAL" solo></v-text-field>
|
||||||
|
|
||||||
<!-- Our only option right now is voice mode, so if not enabled, hide the 'options' drop down as well -->
|
<!-- Our only option right now is voice mode, so if not enabled, hide the 'options' drop down as well -->
|
||||||
<template v-if="$config.experimental_voice_mode || $config.experimental_read_only_room">
|
<template v-if="$config.experimental_voice_mode || $config.experimental_read_only_room">
|
||||||
|
|
@ -71,7 +71,7 @@
|
||||||
|
|
||||||
<div class="error--text" v-if="roomCreationErrorMsg"> {{ roomCreationErrorMsg }}</div>
|
<div class="error--text" v-if="roomCreationErrorMsg"> {{ roomCreationErrorMsg }}</div>
|
||||||
<v-btn id="btn-room-create" color="black" depressed class="filled-button" @click.stop="onCreate"
|
<v-btn id="btn-room-create" color="black" depressed class="filled-button" @click.stop="onCreate"
|
||||||
:disabled="isDisabled">
|
:disabled="isDisabled" ref="create">
|
||||||
<div v-if="status && !enterRoomDialog" class="text-center">
|
<div v-if="status && !enterRoomDialog" class="text-center">
|
||||||
{{ status }}
|
{{ status }}
|
||||||
<v-progress-circular v-if="step == steps.CREATING" indeterminate color="primary"
|
<v-progress-circular v-if="step == steps.CREATING" indeterminate color="primary"
|
||||||
|
|
@ -83,91 +83,8 @@
|
||||||
</v-row>
|
</v-row>
|
||||||
</v-container>
|
</v-container>
|
||||||
|
|
||||||
<v-fade-transition>
|
<interactive-auth ref="interactiveAuth" />
|
||||||
<!-- <div class="section ma-3" flat v-if="step > steps.INITIAL"> -->
|
|
||||||
<!-- <div class="h4 text-left">{{ $t("new_room.join_permissions") }}</div>
|
|
||||||
<div class="h2 text-left">
|
|
||||||
{{ $t("new_room.set_join_permissions") }}
|
|
||||||
</div>
|
|
||||||
<div>{{ $t("new_room.join_permissions_info") }}</div>
|
|
||||||
<v-select
|
|
||||||
:disabled="step >= steps.CREATING"
|
|
||||||
:items="joinRules"
|
|
||||||
class="mt-4"
|
|
||||||
v-model="joinRule"
|
|
||||||
item-value="id"
|
|
||||||
>
|
|
||||||
<template v-slot:selection="{ item }">
|
|
||||||
{{ item.text }}
|
|
||||||
</template>
|
|
||||||
<template v-slot:item="{ item, attrs, on }">
|
|
||||||
<v-list-item v-on="on" v-bind="attrs" #default="{ active }">
|
|
||||||
<v-list-item-avatar>
|
|
||||||
<v-icon class="grey lighten-1" dark>{{ item.icon }}</v-icon>
|
|
||||||
</v-list-item-avatar>
|
|
||||||
<v-list-item-content>
|
|
||||||
<v-list-item-title v-text="item.text"></v-list-item-title>
|
|
||||||
<v-list-item-subtitle
|
|
||||||
v-text="item.descr"
|
|
||||||
></v-list-item-subtitle>
|
|
||||||
</v-list-item-content>
|
|
||||||
<v-list-item-action>
|
|
||||||
<v-btn icon v-if="active">
|
|
||||||
<v-icon color="grey lighten-1">check</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
</v-list-item-action>
|
|
||||||
</v-list-item>
|
|
||||||
</template>
|
|
||||||
</v-select>
|
|
||||||
|
|
||||||
<v-divider style="margin-bottom: 20px" />
|
|
||||||
|
|
||||||
<v-text-field
|
|
||||||
v-if="publicRoomLink"
|
|
||||||
:value="publicRoomLink"
|
|
||||||
class="room-link"
|
|
||||||
readonly
|
|
||||||
filled
|
|
||||||
background-color="transparent"
|
|
||||||
append-icon="content_copy"
|
|
||||||
type="text"
|
|
||||||
@click:append.stop="copyRoomLink"
|
|
||||||
></v-text-field>
|
|
||||||
<v-btn
|
|
||||||
v-else-if="joinRule == 'public'"
|
|
||||||
:loading="step == steps.CREATING"
|
|
||||||
block
|
|
||||||
depressed
|
|
||||||
class="outlined-button"
|
|
||||||
@click.stop="getPublicLink"
|
|
||||||
><v-icon class="me-2">link</v-icon
|
|
||||||
>{{ $t("new_room.get_link") }}</v-btn
|
|
||||||
>
|
|
||||||
<v-btn
|
|
||||||
v-else-if="joinRule == 'invite'"
|
|
||||||
block
|
|
||||||
depressed
|
|
||||||
class="outlined-button"
|
|
||||||
@click.stop="addPeople"
|
|
||||||
><v-icon class="me-2">person_add</v-icon
|
|
||||||
>{{ $t("new_room.add_people") }}</v-btn
|
|
||||||
>
|
|
||||||
|
|
||||||
<div v-if="publicRoomLinkCopied" class="link-copied">
|
|
||||||
{{ $t("new_room.link_copied") }}
|
|
||||||
</div>
|
|
||||||
-->
|
|
||||||
<!-- <div v-if="status && !enterRoomDialog" class="text-center">
|
|
||||||
<v-progress-circular
|
|
||||||
v-if="step == steps.CREATING"
|
|
||||||
indeterminate
|
|
||||||
color="primary"
|
|
||||||
size="20"
|
|
||||||
></v-progress-circular>
|
|
||||||
{{ status }}
|
|
||||||
</div> -->
|
|
||||||
<!-- </div> -->
|
|
||||||
</v-fade-transition>
|
|
||||||
<input id="room-avatar-picker" ref="avatar" type="file" name="avatar" @change="handlePickedAvatar($event)"
|
<input id="room-avatar-picker" ref="avatar" type="file" name="avatar" @change="handlePickedAvatar($event)"
|
||||||
accept="image/*" class="d-none" />
|
accept="image/*" class="d-none" />
|
||||||
<v-dialog v-model="enterRoomDialog" :width="$vuetify.breakpoint.smAndUp ? '50%' : '90%'">
|
<v-dialog v-model="enterRoomDialog" :width="$vuetify.breakpoint.smAndUp ? '50%' : '90%'">
|
||||||
|
|
@ -214,6 +131,7 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import util, { ROOM_TYPE_VOICE_MODE } from "../plugins/utils";
|
import util, { ROOM_TYPE_VOICE_MODE } from "../plugins/utils";
|
||||||
|
import InteractiveAuth from './InteractiveAuth.vue';
|
||||||
import rememberMeMixin from "./rememberMeMixin";
|
import rememberMeMixin from "./rememberMeMixin";
|
||||||
|
|
||||||
const steps = Object.freeze({
|
const steps = Object.freeze({
|
||||||
|
|
@ -225,6 +143,7 @@ const steps = Object.freeze({
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "CreateRoom",
|
name: "CreateRoom",
|
||||||
|
components: { InteractiveAuth },
|
||||||
mixins: [rememberMeMixin],
|
mixins: [rememberMeMixin],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
|
@ -306,7 +225,7 @@ export default {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
|
|
@ -456,7 +375,7 @@ export default {
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.$matrix
|
return this.$matrix
|
||||||
.getLoginPromise()
|
.getLoginPromise(this.$refs.interactiveAuth.registrationFlowHandler)
|
||||||
.then(
|
.then(
|
||||||
function (user) {
|
function (user) {
|
||||||
if (user.is_guest && !hasUser) {
|
if (user.is_guest && !hasUser) {
|
||||||
|
|
@ -628,7 +547,7 @@ export default {
|
||||||
showAvatarPickerList() {
|
showAvatarPickerList() {
|
||||||
this.$refs.avatar.$refs.input.click();
|
this.$refs.avatar.$refs.input.click();
|
||||||
},
|
},
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
||||||
293
src/components/InteractiveAuth.vue
Normal file
293
src/components/InteractiveAuth.vue
Normal file
|
|
@ -0,0 +1,293 @@
|
||||||
|
<template>
|
||||||
|
<v-fade-transition>
|
||||||
|
<v-container fluid class="mt-40" v-if="step == steps.CAPTCHA">
|
||||||
|
<div class="ma-3" flat ref="captcha" id="captcha">
|
||||||
|
</div>
|
||||||
|
</v-container>
|
||||||
|
|
||||||
|
<v-container fluid class="mt-40" v-if="step == steps.ENTER_EMAIL">
|
||||||
|
<v-row cols="12" align="center" justify="center">
|
||||||
|
<v-col sm="8" align="center">
|
||||||
|
<div class="text-left font-weight-light">{{ $t("login.email") }}</div>
|
||||||
|
<v-text-field v-model="email" color="black" :rules="emailRules" type="email" maxlength="200"
|
||||||
|
background-color="white" v-on:keyup.enter="onEmailEntered(email)" autofocus solo></v-text-field>
|
||||||
|
<v-btn :disabled="!emailIsValid" color="black" depressed class="filled-button"
|
||||||
|
@click.stop="onEmailEntered(email)">
|
||||||
|
{{ $t("login.send_verification") }}
|
||||||
|
</v-btn>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-container>
|
||||||
|
|
||||||
|
<v-container fluid class="mt-40" v-if="step == steps.TERMS">
|
||||||
|
<v-row cols="12" align="center" justify="center">
|
||||||
|
<v-col sm="8" align="center">
|
||||||
|
<div class="text-left font-weight-light">{{ $t("login.terms") }}</div>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-row cols="12" align="center" justify="center">
|
||||||
|
<v-col sm="8" align="center">
|
||||||
|
<div v-for="(policy) in this.policies" :key="policy.id">
|
||||||
|
<v-checkbox class="mt-0" v-model="policy.accepted">
|
||||||
|
<template v-slot:label>
|
||||||
|
<a target="_blank" :href="policy.url" @click.stop>{{ policy.name }}
|
||||||
|
</a>
|
||||||
|
</template>
|
||||||
|
</v-checkbox>
|
||||||
|
</div>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-row cols="12" align="center" justify="center">
|
||||||
|
<v-col sm="8" align="center">
|
||||||
|
<v-btn color="black" :disabled="!this.allPoliciesAccepted" depressed class="filled-button"
|
||||||
|
@click.stop="onPoliciesAccepted()">
|
||||||
|
{{ $t("login.accept_terms") }}
|
||||||
|
</v-btn>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-container>
|
||||||
|
|
||||||
|
<v-container fluid class="mt-40" v-if="step == steps.AWAITING_EMAIL_VERIFICATION">
|
||||||
|
<v-row cols="12" align="center" justify="center">
|
||||||
|
<v-col sm="8" align="center">
|
||||||
|
<div>
|
||||||
|
<div class="text-left font-weight-light">{{ $t("login.sent_verification", { email: this.email }) }}</div>
|
||||||
|
<v-progress-circular style="display: inline-flex" indeterminate color="primary"
|
||||||
|
size="20"></v-progress-circular>
|
||||||
|
</div>
|
||||||
|
<v-btn color="black" depressed class="filled-button" @click.stop="onEmailResend()">
|
||||||
|
{{ $t("login.resend_verification") }}
|
||||||
|
</v-btn>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-container>
|
||||||
|
</v-fade-transition>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import util from "../plugins/utils";
|
||||||
|
|
||||||
|
const steps = Object.freeze({
|
||||||
|
INITIAL: 0,
|
||||||
|
CAPTCHA: 1,
|
||||||
|
TERMS: 2,
|
||||||
|
EXTERNAL_AUTH: 3,
|
||||||
|
ENTER_EMAIL: 4,
|
||||||
|
AWAITING_EMAIL_VERIFICATION: 5,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "InteractiveAuth",
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
steps,
|
||||||
|
step: steps.INITIAL,
|
||||||
|
emailRules: [
|
||||||
|
v => this.validEmail(v) || this.$t("login.email_not_valid")
|
||||||
|
],
|
||||||
|
policies: null,
|
||||||
|
onPoliciesAccepted: () => { },
|
||||||
|
onEmailResend: () => { },
|
||||||
|
onEmailVerify: () => { },
|
||||||
|
email: "",
|
||||||
|
emailVerification: "",
|
||||||
|
emailVerificationSecret: util.randomUser("tokn"),
|
||||||
|
emailVerificationAttempt: 1,
|
||||||
|
emailVerificationSid: null,
|
||||||
|
emailVerificationPollTimer: null,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
allPoliciesAccepted() {
|
||||||
|
return Object.keys(this.policies).every(id => this.policies[id].accepted);
|
||||||
|
},
|
||||||
|
emailIsValid() {
|
||||||
|
return this.validEmail(this.email);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
validEmail(email) {
|
||||||
|
if (/^\w+([.-]?\w+)*@\w+([.-]?\w+)*(.\w{2,3})+$/.test(email)) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
registrationFlowHandler(client, authData) {
|
||||||
|
const flows = authData.flows;
|
||||||
|
if (!flows || flows.length == 0) {
|
||||||
|
return Promise.reject(this.$t('login.no_supported_flow'));
|
||||||
|
}
|
||||||
|
const currentFlow = flows[0];
|
||||||
|
if (!currentFlow || !currentFlow.stages || currentFlow.stages.length == 0) {
|
||||||
|
return Promise.reject(this.$t('login.no_supported_flow'));
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO - For now hardcoded to flows[0]
|
||||||
|
const completedStages = authData.completed || [];
|
||||||
|
const remainingStages = currentFlow.stages.filter((stage) => {
|
||||||
|
return !completedStages.includes(stage);
|
||||||
|
});
|
||||||
|
|
||||||
|
const nextStage = remainingStages[0];
|
||||||
|
|
||||||
|
const submitStageData = (resolve, reject, data) => {
|
||||||
|
this.step = steps.CREATING;
|
||||||
|
resolve(client.registerRequest({ auth: data }).catch(err => {
|
||||||
|
if (err.httpStatus == 401 && err.data) {
|
||||||
|
// Start next stage!
|
||||||
|
return this.registrationFlowHandler(client, err.data);
|
||||||
|
} else {
|
||||||
|
reject(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
));
|
||||||
|
};
|
||||||
|
|
||||||
|
switch (nextStage) {
|
||||||
|
case "m.login.recaptcha":
|
||||||
|
this.step = steps.CAPTCHA;
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
let script = document.createElement("script");
|
||||||
|
script.src = "https://www.google.com/recaptcha/api.js?onload=onReCaptchaLoaded&render=explicit";
|
||||||
|
window.onReCaptchaLoaded = (ignoredev) => {
|
||||||
|
const params = authData.params[nextStage];
|
||||||
|
if (!params || !params.public_key) {
|
||||||
|
return reject(this.$t('login.no_supported_flow'));
|
||||||
|
}
|
||||||
|
const site_key = params.public_key;
|
||||||
|
window.grecaptcha.render('captcha', {
|
||||||
|
'sitekey': site_key,
|
||||||
|
'theme': 'light',
|
||||||
|
callback: (captcha_response) => {
|
||||||
|
const data = {
|
||||||
|
session: authData.session,
|
||||||
|
type: nextStage,
|
||||||
|
response: captcha_response
|
||||||
|
};
|
||||||
|
submitStageData(resolve, reject, data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
document.body.append(script);
|
||||||
|
});
|
||||||
|
case "m.login.terms":
|
||||||
|
this.step = steps.TERMS;
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const params = authData.params[nextStage];
|
||||||
|
if (!params || !params.policies) {
|
||||||
|
return reject(this.$t('login.no_supported_flow'));
|
||||||
|
}
|
||||||
|
|
||||||
|
let policies = [];
|
||||||
|
Object.keys(params.policies).forEach((policyId) => {
|
||||||
|
const policy = params.policies[policyId];
|
||||||
|
let localized = policy[this.$i18n.locale] || policy["en"] || policy[Object.keys(policy).filter(k => k != "version")[0]];
|
||||||
|
if (!localized) {
|
||||||
|
// Did not find info for this policy!
|
||||||
|
return reject(this.$t('login.no_supported_flow'));
|
||||||
|
}
|
||||||
|
policies.push({
|
||||||
|
id: policyId,
|
||||||
|
name: localized.name,
|
||||||
|
url: localized.url,
|
||||||
|
accepted: false
|
||||||
|
});
|
||||||
|
});
|
||||||
|
this.onPoliciesAccepted = () => {
|
||||||
|
// Button should not be enabled if not all are accepted, but double check here...
|
||||||
|
if (this.allPoliciesAccepted) {
|
||||||
|
const data = {
|
||||||
|
session: authData.session,
|
||||||
|
type: nextStage,
|
||||||
|
};
|
||||||
|
submitStageData(resolve, reject, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.policies = policies;
|
||||||
|
});
|
||||||
|
|
||||||
|
case "m.login.email.identity":
|
||||||
|
this.step = steps.ENTER_EMAIL;
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
this.onEmailEntered = (email) => {
|
||||||
|
this.step = steps.CREATING;
|
||||||
|
this.emailVerificationAttempt = 1;
|
||||||
|
client.requestRegisterEmailToken(email, this.emailVerificationSecret, this.emailVerificationAttempt, null)
|
||||||
|
.then(response => {
|
||||||
|
this.emailVerificationSid = response.sid;
|
||||||
|
this.step = steps.AWAITING_EMAIL_VERIFICATION;
|
||||||
|
this.onEmailResend = () => {
|
||||||
|
this.emailVerificationAttempt += 1;
|
||||||
|
client.requestRegisterEmailToken(email, this.emailVerificationSecret, this.emailVerificationAttempt, null)
|
||||||
|
.then(response => {
|
||||||
|
this.emailVerificationSid = response.sid; // Update the SID (TODO: will it really change?)
|
||||||
|
})
|
||||||
|
.catch(ignorederr => { });
|
||||||
|
},
|
||||||
|
this.onEmailVerify = async () => {
|
||||||
|
const data = {
|
||||||
|
type: nextStage,
|
||||||
|
threepid_creds: {
|
||||||
|
sid: this.emailVerificationSid,
|
||||||
|
client_secret: this.emailVerificationSecret
|
||||||
|
},
|
||||||
|
session: authData.session,
|
||||||
|
};
|
||||||
|
console.log("Polling...");
|
||||||
|
await client.registerRequest({ auth: data })
|
||||||
|
.then((result) => {
|
||||||
|
// Yes, email is verified!
|
||||||
|
clearTimeout(this.emailVerificationPollTimer);
|
||||||
|
this.emailVerificationPollTimer = null;
|
||||||
|
resolve(result);
|
||||||
|
})
|
||||||
|
.catch(ignorederr => {
|
||||||
|
// Continue polling
|
||||||
|
});
|
||||||
|
};
|
||||||
|
this.emailVerificationPollTimer = setInterval(this.onEmailVerify, 5000);
|
||||||
|
})
|
||||||
|
.catch((ignorederr) => {
|
||||||
|
console.error("ERROR", ignorederr);
|
||||||
|
return reject(this.$t('login.no_supported_flow'))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
default:
|
||||||
|
this.step = steps.EXTERNAL_AUTH;
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
let fallbackUrl = client.getFallbackAuthUrl(nextStage, authData.session);
|
||||||
|
var popupWindow;
|
||||||
|
|
||||||
|
var eventListener = function (ev) {
|
||||||
|
// check it's the right message from the right place.
|
||||||
|
if (ev.data !== "authDone" || !fallbackUrl.startsWith(ev.origin)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// close the popup
|
||||||
|
popupWindow.close();
|
||||||
|
window.removeEventListener("message", eventListener);
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
session: authData.session,
|
||||||
|
};
|
||||||
|
submitStageData(resolve, reject, data);
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener("message", eventListener);
|
||||||
|
popupWindow = window.open(fallbackUrl, "_blank", "popup=true");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
@import "@/assets/css/chat.scss";
|
||||||
|
</style>
|
||||||
|
|
@ -80,6 +80,8 @@
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
|
|
||||||
|
<interactive-auth ref="interactiveAuth" />
|
||||||
|
|
||||||
<v-btn id="btn-join" class="btn-dark" large @click.stop="handleJoin" :loading="loading" v-if="!currentUser">{{
|
<v-btn id="btn-join" class="btn-dark" large @click.stop="handleJoin" :loading="loading" v-if="!currentUser">{{
|
||||||
roomId && roomId.startsWith("@") ? $t("join.enter_room_user") : $t("join.enter_room")
|
roomId && roomId.startsWith("@") ? $t("join.enter_room_user") : $t("join.enter_room")
|
||||||
}}</v-btn>
|
}}</v-btn>
|
||||||
|
|
@ -133,6 +135,7 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import util from "../plugins/utils";
|
import util from "../plugins/utils";
|
||||||
|
import InteractiveAuth from './InteractiveAuth.vue';
|
||||||
import LanguageMixin from "./languageMixin";
|
import LanguageMixin from "./languageMixin";
|
||||||
import rememberMeMixin from "./rememberMeMixin";
|
import rememberMeMixin from "./rememberMeMixin";
|
||||||
|
|
||||||
|
|
@ -143,6 +146,7 @@ export default {
|
||||||
mixins: [LanguageMixin, rememberMeMixin],
|
mixins: [LanguageMixin, rememberMeMixin],
|
||||||
components: {
|
components: {
|
||||||
SelectLanguageDialog,
|
SelectLanguageDialog,
|
||||||
|
InteractiveAuth
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
|
@ -340,7 +344,7 @@ export default {
|
||||||
const hasUser = this.currentUser ? true : false;
|
const hasUser = this.currentUser ? true : false;
|
||||||
var setProfileData = false;
|
var setProfileData = false;
|
||||||
return this.$matrix
|
return this.$matrix
|
||||||
.getLoginPromise()
|
.getLoginPromise(this.$refs.interactiveAuth.registrationFlowHandler)
|
||||||
.then(
|
.then(
|
||||||
function (user) {
|
function (user) {
|
||||||
if (user.is_guest && !hasUser) {
|
if (user.is_guest && !hasUser) {
|
||||||
|
|
|
||||||
|
|
@ -37,10 +37,15 @@
|
||||||
:error="userErrorMessage != null"
|
:error="userErrorMessage != null"
|
||||||
:error-messages="userErrorMessage"
|
:error-messages="userErrorMessage"
|
||||||
required
|
required
|
||||||
v-on:keyup.enter="$refs.password.focus()"
|
v-on:keyup.enter="onUsernameEnter"
|
||||||
v-on:keydown="hasError=false"
|
v-on:keydown="hasError=false"
|
||||||
|
v-on:blur="onUsernameBlur"
|
||||||
></v-text-field>
|
></v-text-field>
|
||||||
|
|
||||||
|
<div class="error--text" v-if="loadingLoginFlows">Loading login flows...</div>
|
||||||
|
|
||||||
<v-text-field
|
<v-text-field
|
||||||
|
v-show="showPasswordField"
|
||||||
prepend-inner-icon="$vuetify.icons.password"
|
prepend-inner-icon="$vuetify.icons.password"
|
||||||
ref="password"
|
ref="password"
|
||||||
v-model="user.password"
|
v-model="user.password"
|
||||||
|
|
@ -72,7 +77,7 @@
|
||||||
/>
|
/>
|
||||||
<v-btn
|
<v-btn
|
||||||
id="btn-login"
|
id="btn-login"
|
||||||
:disabled="!isValid || loading"
|
:disabled="!isValid || loading || loadingLoginFlows"
|
||||||
color="black"
|
color="black"
|
||||||
depressed
|
depressed
|
||||||
block
|
block
|
||||||
|
|
@ -100,6 +105,7 @@
|
||||||
import User from "../models/user";
|
import User from "../models/user";
|
||||||
import util from "../plugins/utils";
|
import util from "../plugins/utils";
|
||||||
import rememberMeMixin from "./rememberMeMixin";
|
import rememberMeMixin from "./rememberMeMixin";
|
||||||
|
import * as sdk from "matrix-js-sdk";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "Login",
|
name: "Login",
|
||||||
|
|
@ -112,7 +118,11 @@ export default {
|
||||||
message: "",
|
message: "",
|
||||||
userErrorMessage: null,
|
userErrorMessage: null,
|
||||||
passErrorMessage: null,
|
passErrorMessage: null,
|
||||||
hasError: false
|
hasError: false,
|
||||||
|
currentLoginServer: "",
|
||||||
|
loadingLoginFlows: false,
|
||||||
|
loginFlows: null,
|
||||||
|
showPasswordField: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
|
@ -124,7 +134,7 @@ export default {
|
||||||
},
|
},
|
||||||
showCloseButton() {
|
showCloseButton() {
|
||||||
return this.$navigation && this.$navigation.canPop();
|
return this.$navigation && this.$navigation.canPop();
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
if (this.loggedIn) {
|
if (this.loggedIn) {
|
||||||
|
|
@ -158,7 +168,7 @@ export default {
|
||||||
user.normalize();
|
user.normalize();
|
||||||
|
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
this.$store.dispatch("login", user).then(
|
this.$store.dispatch("login", { user }).then(
|
||||||
() => {
|
() => {
|
||||||
if (this.$matrix.currentRoomId) {
|
if (this.$matrix.currentRoomId) {
|
||||||
this.$navigation.push(
|
this.$navigation.push(
|
||||||
|
|
@ -175,12 +185,13 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
(error) => {
|
(error) => {
|
||||||
|
console.error(error);
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
this.message =
|
this.message =
|
||||||
(error.data && error.data.error) ||
|
(error.data && error.data.error) ||
|
||||||
error.message ||
|
error.message ||
|
||||||
error.toString();
|
error.toString();
|
||||||
if(error.data.errcode ==='M_FORBIDDEN') {
|
if(error.data && error.data.errcode ==='M_FORBIDDEN') {
|
||||||
this.message = this.$i18n.messages[this.$i18n.locale].login.invalid_message;
|
this.message = this.$i18n.messages[this.$i18n.locale].login.invalid_message;
|
||||||
this.hasError = true;
|
this.hasError = true;
|
||||||
}
|
}
|
||||||
|
|
@ -192,6 +203,45 @@ export default {
|
||||||
handleCreateRoom() {
|
handleCreateRoom() {
|
||||||
this.$navigation.push({ name: "CreateRoom" });
|
this.$navigation.push({ name: "CreateRoom" });
|
||||||
},
|
},
|
||||||
|
onUsernameEnter() {
|
||||||
|
this.$refs.password.focus();
|
||||||
|
this.onUsernameBlur();
|
||||||
|
},
|
||||||
|
onUsernameBlur() {
|
||||||
|
var user = Object.assign({}, this.user);
|
||||||
|
user.normalize();
|
||||||
|
const server = user.home_server || this.$config.defaultServer;
|
||||||
|
if (server !== this.currentLoginServer) {
|
||||||
|
|
||||||
|
this.showPasswordField = false;
|
||||||
|
|
||||||
|
this.currentLoginServer = server;
|
||||||
|
this.loadingLoginFlows = true;
|
||||||
|
|
||||||
|
const matrixClient = sdk.createClient({baseUrl: server});
|
||||||
|
matrixClient.loginFlows().then((response) => {
|
||||||
|
console.log("FLOWS", response.flows);
|
||||||
|
this.loginFlows = response.flows.filter(this.supportedLoginFlow);
|
||||||
|
this.loadingLoginFlows = false;
|
||||||
|
if (this.loginFlows.length == 0) {
|
||||||
|
this.message = this.$t('login.no_supported_flow')
|
||||||
|
this.hasError = true;
|
||||||
|
} else {
|
||||||
|
this.message = "";
|
||||||
|
this.hasError = false;
|
||||||
|
this.showPasswordField = this.loginFlows.some(f => f.type == "m.login.password");
|
||||||
|
if (this.showPasswordField) {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.$refs.password.focus();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
supportedLoginFlow(flow) {
|
||||||
|
return ["m.login.password"].includes(flow.type);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -268,7 +268,7 @@ class Util {
|
||||||
resolve(true);
|
resolve(true);
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
console.log("Image send error: ", err);
|
console.log("Send error: ", err);
|
||||||
if (err && err.name == "UnknownDeviceError") {
|
if (err && err.name == "UnknownDeviceError") {
|
||||||
console.log("Unknown devices. Mark as known before retrying.");
|
console.log("Unknown devices. Mark as known before retrying.");
|
||||||
var setAsKnownPromises = [];
|
var setAsKnownPromises = [];
|
||||||
|
|
@ -290,7 +290,18 @@ class Util {
|
||||||
Promise.all(setAsKnownPromises)
|
Promise.all(setAsKnownPromises)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
// All devices now marked as "known", try to resend
|
// All devices now marked as "known", try to resend
|
||||||
matrixClient.resendEvent(err.event, matrixClient.getRoom(err.event.getRoomId()))
|
let event = err.event;
|
||||||
|
if (!event) {
|
||||||
|
// Seems event is no longer send in the UnknownDevices error...
|
||||||
|
const room = matrixClient.getRoom(roomId);
|
||||||
|
if (room) {
|
||||||
|
event = room.getLiveTimeline().getEvents().find(e => {
|
||||||
|
// Find the exact match (= object equality)
|
||||||
|
return e.error === err
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
matrixClient.resendEvent(event, matrixClient.getRoom(event.getRoomId()))
|
||||||
.then((result) => {
|
.then((result) => {
|
||||||
console.log("Message sent: ", result);
|
console.log("Message sent: ", result);
|
||||||
resolve(true);
|
resolve(true);
|
||||||
|
|
|
||||||
|
|
@ -104,35 +104,59 @@ export default {
|
||||||
console.log("create crypto store");
|
console.log("create crypto store");
|
||||||
return new LocalStorageCryptoStore(this.$store.getters.storage);
|
return new LocalStorageCryptoStore(this.$store.getters.storage);
|
||||||
},
|
},
|
||||||
login(user) {
|
login(user, registrationFlowHandler) {
|
||||||
const tempMatrixClient = sdk.createClient({baseUrl: user.home_server});
|
const tempMatrixClient = sdk.createClient({baseUrl: user.home_server, idBaseUrl: this.$config.identityServer});
|
||||||
var promiseLogin;
|
var promiseLogin;
|
||||||
|
|
||||||
const self = this;
|
const self = this;
|
||||||
if (user.access_token) {
|
if (user.access_token) {
|
||||||
// Logged in on "real" account
|
// Logged in on "real" account
|
||||||
promiseLogin = Promise.resolve(user);
|
promiseLogin = Promise.resolve(user);
|
||||||
} else if (user.is_guest && !user.user_id) {
|
} else if (user.is_guest && (!user.user_id || user.registration_session)) {
|
||||||
// Generate random username and password. We don't user REAL matrix
|
// Generate random username and password. We don't user REAL matrix
|
||||||
// guest accounts because 1. They are not allowed to post media, 2. They
|
// guest accounts because 1. They are not allowed to post media, 2. They
|
||||||
// can not use avatars and 3. They can not seamlessly be upgraded to real accounts.
|
// can not use avatars and 3. They can not seamlessly be upgraded to real accounts.
|
||||||
//
|
//
|
||||||
// Instead, we use an ILAG approach, Improved Landing as Guest.
|
// Instead, we use an ILAG approach, Improved Landing as Guest.
|
||||||
const user = util.randomUser(this.$config.userIdPrefix);
|
const userId = user.registration_session ? user.user_id : util.randomUser(this.$config.userIdPrefix);
|
||||||
const pass = util.randomPass();
|
const pass = user.registration_session ? user.password : util.randomPass();
|
||||||
promiseLogin = tempMatrixClient
|
|
||||||
.register(user, pass, null, {
|
const extractAndSaveUser = (response) => {
|
||||||
type: "m.login.dummy",
|
|
||||||
initial_device_display_name: this.$config.appName,
|
|
||||||
})
|
|
||||||
.then((response) => {
|
|
||||||
console.log("Response", response);
|
|
||||||
var u = Object.assign({}, response);
|
var u = Object.assign({}, response);
|
||||||
u.home_server = tempMatrixClient.baseUrl; // Don't use deprecated field from response.
|
u.home_server = tempMatrixClient.baseUrl; // Don't use deprecated field from response.
|
||||||
u.password = pass;
|
u.password = pass;
|
||||||
u.is_guest = true;
|
u.is_guest = true;
|
||||||
this.$store.commit("setUser", u);
|
this.$store.commit("setUser", u);
|
||||||
return u;
|
return u;
|
||||||
|
};
|
||||||
|
|
||||||
|
promiseLogin = tempMatrixClient
|
||||||
|
.register(userId, pass, user.registration_session || null, {
|
||||||
|
type: "m.login.dummy",
|
||||||
|
initial_device_display_name: this.$config.appName,
|
||||||
|
})
|
||||||
|
.then((response) => {
|
||||||
|
return extractAndSaveUser(response);
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
if (registrationFlowHandler && error.httpStatus == 401 && error.data) {
|
||||||
|
const registrationSession = error.data.session;
|
||||||
|
|
||||||
|
// Store user, pass and session, so we can resume if network failure occurs etc.
|
||||||
|
//
|
||||||
|
var u = {};
|
||||||
|
u.user_id = userId;
|
||||||
|
u.home_server = tempMatrixClient.baseUrl; // Don't use deprecated field from response.
|
||||||
|
u.password = pass;
|
||||||
|
u.is_guest = true;
|
||||||
|
u.registration_session = registrationSession;
|
||||||
|
this.$store.commit("setUser", u);
|
||||||
|
|
||||||
|
return registrationFlowHandler(tempMatrixClient, error.data).then((response) => extractAndSaveUser(response));
|
||||||
|
} else {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
var data = {
|
var data = {
|
||||||
|
|
@ -286,11 +310,11 @@ export default {
|
||||||
* Will use a real account, if we have one, otherwise will create
|
* Will use a real account, if we have one, otherwise will create
|
||||||
* a random account.
|
* a random account.
|
||||||
*/
|
*/
|
||||||
getLoginPromise() {
|
getLoginPromise(registrationFlowHandler) {
|
||||||
if (this.ready) {
|
if (this.ready) {
|
||||||
return Promise.resolve(this.currentUser);
|
return Promise.resolve(this.currentUser);
|
||||||
}
|
}
|
||||||
return this.$store.dispatch("login", this.currentUser || new User(this.$config.defaultServer, "", "", true));
|
return this.$store.dispatch("login", { user: this.currentUser || new User(this.$config.defaultServer, "", "", true), registrationFlowHandler });
|
||||||
},
|
},
|
||||||
|
|
||||||
addMatrixClientListeners(client) {
|
addMatrixClientListeners(client) {
|
||||||
|
|
@ -432,10 +456,14 @@ export default {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
Vue.set(this, "rooms", updatedRooms);
|
Vue.set(this, "rooms", updatedRooms);
|
||||||
const currentRoom = this.getRoom(this.$store.state.currentRoomId);
|
|
||||||
|
const resolvedId = (this.currentRoomId && this.currentRoomId.startsWith("#")) ? this.matrixClient.resolveRoomAlias(this.currentRoomId).then(r => r.room_id) : Promise.resolve(this.currentRoomId);
|
||||||
|
resolvedId.then(roomId => {
|
||||||
|
const currentRoom = this.getRoom(roomId);
|
||||||
if (this.currentRoom != currentRoom) {
|
if (this.currentRoom != currentRoom) {
|
||||||
this.currentRoom = currentRoom;
|
this.currentRoom = currentRoom;
|
||||||
}
|
}
|
||||||
|
}).catch(ignorederror => {});
|
||||||
},
|
},
|
||||||
|
|
||||||
setCurrentRoomId(roomId) {
|
setCurrentRoomId(roomId) {
|
||||||
|
|
@ -560,6 +588,28 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the current user is joined to the given room.
|
||||||
|
* @param roomIdOrAlias
|
||||||
|
* @returns Promise<Bool> - Whether the user is joined to the room or not
|
||||||
|
*/
|
||||||
|
isJoinedToRoom(roomIdOrAlias) {
|
||||||
|
if (roomIdOrAlias && this.matrixClient) {
|
||||||
|
try {
|
||||||
|
const resolvedRoomId = roomIdOrAlias.startsWith("#") ? this.matrixClient.resolveRoomAlias(roomIdOrAlias).then(res => res.room_id) : Promise.resolve(roomIdOrAlias);
|
||||||
|
return resolvedRoomId.then(roomId => {
|
||||||
|
return this.matrixClient.getJoinedRooms().then(rooms => {
|
||||||
|
return rooms.joined_rooms.includes(roomId);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} catch (ignorederror) {
|
||||||
|
console.error(ignorederror);
|
||||||
|
return Promise.resolve(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Promise.resolve(false);
|
||||||
|
},
|
||||||
|
|
||||||
isReadOnlyRoom(roomId) {
|
isReadOnlyRoom(roomId) {
|
||||||
if (this.matrixClient && roomId) {
|
if (this.matrixClient && roomId) {
|
||||||
const room = this.getRoom(roomId);
|
const room = this.getRoom(roomId);
|
||||||
|
|
|
||||||
|
|
@ -101,8 +101,8 @@ export default new Vuex.Store({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
login({ commit }, user) {
|
login({ commit }, { user, registrationFlowHandler }) {
|
||||||
return this._vm.$matrix.login(user).then(
|
return this._vm.$matrix.login(user, registrationFlowHandler).then(
|
||||||
user => {
|
user => {
|
||||||
commit('loginSuccess', user);
|
commit('loginSuccess', user);
|
||||||
return Promise.resolve(user);
|
return Promise.resolve(user);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue