Expire login info - request re-login.

closes #1015
This commit is contained in:
Matthias 2022-12-10 12:51:28 +01:00
parent 210c7bd692
commit 594a828358
7 changed files with 126 additions and 26 deletions

View File

@ -21,14 +21,22 @@
<LoggedOutIcon
v-else
class="offline"
title="Login info expied, please login again."
title="Login info expired, please login again."
></LoggedOutIcon>
</b-form-checkbox>
<div v-if="!noButtons" class="float-end d-flex flex-align-center">
<b-button class="ms-1" size="sm" title="Delete bot" @click="$emit('edit')">
<b-button
v-if="botStore.botStores[bot.botId].isBotLoggedIn"
class="ms-1"
size="sm"
title="Edit bot"
@click="$emit('edit')"
>
<EditIcon :size="16" />
</b-button>
<b-button v-else class="ms-1" size="sm" title="Login again" @click="$emit('editLogin')">
<LoginIcon :size="16" />
</b-button>
<b-button class="ms-1" size="sm" title="Delete bot" @click="botRemoveModalVisible = true">
<DeleteIcon :size="16" title="Delete Bot" />
</b-button>
@ -48,6 +56,7 @@
<script lang="ts">
import EditIcon from 'vue-material-design-icons/Pencil.vue';
import LoginIcon from 'vue-material-design-icons/Login.vue';
import DeleteIcon from 'vue-material-design-icons/Delete.vue';
import OnlineIcon from 'vue-material-design-icons/Circle.vue';
import LoggedOutIcon from 'vue-material-design-icons/Cancel.vue';
@ -60,6 +69,7 @@ export default defineComponent({
components: {
DeleteIcon,
EditIcon,
LoginIcon,
OnlineIcon,
LoggedOutIcon,
},
@ -67,7 +77,7 @@ export default defineComponent({
bot: { required: true, type: Object as () => BotDescriptor },
noButtons: { default: false, type: Boolean },
},
emits: ['edit'],
emits: ['edit', 'editLogin'],
setup(props) {
const botStore = useBotStore();

View File

@ -7,7 +7,9 @@
:key="bot.botId"
:active="bot.botId === botStore.selectedBot"
button
:title="`${bot.botId} - ${bot.botName} - ${bot.botUrl}`"
:title="`${bot.botId} - ${bot.botName} - ${bot.botUrl} - ${
botStore.botStores[bot.botId].isBotLoggedIn ? '' : 'Login info expired!'
}`"
@click="botStore.selectBot(bot.botId)"
>
<bot-rename
@ -17,10 +19,16 @@
@cancelled="stopEditBot(bot.botId)"
/>
<bot-entry v-else :bot="bot" :no-buttons="small" @edit="editBot(bot.botId)" />
<bot-entry
v-else
:bot="bot"
:no-buttons="small"
@edit="editBot(bot.botId)"
@editLogin="editBotLogin(bot.botId)"
/>
</b-list-group-item>
</b-list-group>
<LoginModal v-if="!small" class="mt-2" login-text="Add new bot" />
<LoginModal v-if="!small" ref="loginModal" class="mt-2" login-text="Add new bot" />
</div>
</template>
@ -31,6 +39,7 @@ import BotRename from '@/components/BotRename.vue';
import { defineComponent, ref } from 'vue';
import { useBotStore } from '@/stores/ftbotwrapper';
import { AuthStorageWithBotId } from '@/types';
export default defineComponent({
name: 'BotList',
@ -42,6 +51,7 @@ export default defineComponent({
const botStore = useBotStore();
const editingBots = ref<string[]>([]);
const loginModal = ref<typeof LoginModal>();
const editBot = (botId: string) => {
if (!editingBots.value.includes(botId)) {
@ -49,6 +59,14 @@ export default defineComponent({
}
};
const editBotLogin = (botId: string) => {
const loginInfo: AuthStorageWithBotId = {
...botStore.botStores[botId].getLoginInfo(),
botId,
};
loginModal.value?.openLoginModal(loginInfo);
};
const stopEditBot = (botId: string) => {
if (!editingBots.value.includes(botId)) {
return;
@ -61,7 +79,9 @@ export default defineComponent({
botStore,
editingBots,
editBot,
editBotLogin,
stopEditBot,
loginModal,
};
},
});

View File

@ -76,7 +76,7 @@
<script setup lang="ts">
import { useUserService } from '@/shared/userService';
import { AuthPayload } from '@/types';
import { AuthPayload, AuthStorageWithBotId } from '@/types';
import { ref } from 'vue';
import { useBotStore } from '@/stores/ftbotwrapper';
@ -85,6 +85,7 @@ import axios from 'axios';
const props = defineProps({
inModal: { default: false, type: Boolean },
existingAuth: { default: null, required: false, type: Object as () => AuthStorageWithBotId },
});
const emit = defineEmits(['loginResult']);
@ -100,6 +101,7 @@ const urlState = ref<boolean | ''>('');
const errorMessage = ref<string>('');
const errorMessageCORS = ref<boolean>(false);
const formRef = ref<HTMLFormElement>();
const botEdit = ref<boolean>(false);
const auth = ref<AuthPayload>({
botName: '',
url: defaultURL,
@ -126,6 +128,7 @@ const resetLogin = () => {
pwdState.value = '';
urlState.value = '';
errorMessage.value = '';
botEdit.value = false;
};
const handleReset = (evt) => {
@ -138,21 +141,35 @@ const handleSubmit = async () => {
return;
}
errorMessage.value = '';
const userService = useUserService(botStore.nextBotId);
// Push the name to submitted names
try {
await userService.login(auth.value);
const botId = botStore.nextBotId;
botStore.addBot({
botName: auth.value.botName,
botId,
botUrl: auth.value.url,
});
// switch to newly added bot
botStore.selectBot(botId);
if (botEdit.value) {
// Bot editing ...
const botId = props.existingAuth.botId;
const userService = useUserService(botId);
try {
await userService.refreshLogin(auth.value);
botStore.botStores[botId].isBotLoggedIn = true;
// botStore.allRefreshFull();
} catch (e) {
console.error(e);
}
} else {
// Add new bot
const botId = botStore.nextBotId;
const userService = useUserService(botId);
await userService.login(auth.value);
botStore.addBot({
botName: auth.value.botName,
botId,
botUrl: auth.value.url,
});
// switch to newly added bot
botStore.selectBot(botId);
emitLoginResult(true);
botStore.allRefreshFull();
}
emitLoginResult(true);
botStore.allRefreshFull();
if (props.inModal === false) {
if (typeof route?.query.redirect === 'string') {
const resolved = router.resolve({ path: route.query.redirect });
@ -191,9 +208,20 @@ const handleOk = (evt) => {
evt.preventDefault();
handleSubmit();
};
const reset = () => {
resetLogin();
console.log('reset ', props.existingAuth);
if (props.existingAuth) {
botEdit.value = true;
auth.value.botName = props.existingAuth.botName;
auth.value.url = props.existingAuth.apiUrl;
auth.value.username = props.existingAuth.username ?? '';
}
};
defineExpose({
handleSubmit,
reset,
});
</script>

View File

@ -66,7 +66,7 @@ export class UserService {
* Retrieve Login info object for the given bot
* @returns Login Info object
*/
private getLoginInfo(): AuthStorage {
public getLoginInfo(): AuthStorage {
const info = UserService.getAllLoginInfos();
if (this.botId in info && 'apiUrl' in info[this.botId] && 'refreshToken' in info[this.botId]) {
return info[this.botId];
@ -74,6 +74,7 @@ export class UserService {
return {
botName: '',
apiUrl: '',
username: '',
refreshToken: '',
accessToken: '',
autoRefresh: false,
@ -131,7 +132,7 @@ export class UserService {
this.removeLoginInfo();
}
public async login(auth: AuthPayload) {
private async loginCall(auth: AuthPayload): Promise<AuthStorage> {
// Login using username / password
const { data } = await axios.post<{}, AxiosResponse<AuthResponse>>(
`${auth.url}/api/v1/token/login`,
@ -149,7 +150,26 @@ export class UserService {
refreshToken: data.refresh_token || '',
autoRefresh: true,
};
return Promise.resolve(obj);
}
return Promise.reject('login failed');
}
public async refreshLogin(auth: AuthPayload) {
try {
const obj = await this.loginCall(auth);
this.storeLoginInfo(obj);
} catch (e) {
console.error(e);
}
}
public async login(auth: AuthPayload) {
try {
const obj = await this.loginCall(auth);
this.storeLoginInfo(obj);
} catch (e) {
console.error(e);
}
}

View File

@ -183,6 +183,9 @@ export function createBotSubStore(botId: string, botName: string) {
logout() {
userService.logout();
},
getLoginInfo() {
return userService.getLoginInfo();
},
rename(name: string) {
userService.renameBot(name);
},

View File

@ -24,6 +24,10 @@ export interface AuthStorage {
autoRefresh: boolean;
}
export interface AuthStorageWithBotId extends AuthStorage {
botId: string;
}
/** Auth Storage container */
export interface AuthStorageMulti {
[key: string]: AuthStorage;

View File

@ -1,26 +1,31 @@
<template>
<div>
<b-button @click="loginViewOpen = true">{{ loginText }}</b-button>
<b-button @click="openLoginModal()"
><LoginIcon :size="16" class="me-1" />{{ loginText }}</b-button
>
<b-modal
id="modal-prevent-closing"
v-model="loginViewOpen"
title="Login to your bot"
@ok="handleOk"
>
<login ref="loginForm" in-modal @loginResult="handleLoginResult" />
<login ref="loginForm" in-modal :existing-auth="loginInfo" @loginResult="handleLoginResult" />
</b-modal>
</div>
</template>
<script setup lang="ts">
import Login from '@/components/Login.vue';
import { ref } from 'vue';
import { AuthStorageWithBotId } from '@/types';
import { nextTick, ref } from 'vue';
import LoginIcon from 'vue-material-design-icons/Login.vue';
defineProps({
loginText: { required: false, default: 'Login', type: String },
});
const loginViewOpen = ref(false);
const loginForm = ref<HTMLFormElement>();
const loginForm = ref<typeof Login>();
const loginInfo = ref<AuthStorageWithBotId | undefined>(undefined);
const handleLoginResult = (result: boolean) => {
if (result) {
loginViewOpen.value = false;
@ -29,6 +34,16 @@ const handleLoginResult = (result: boolean) => {
const handleOk = () => {
loginForm.value?.handleSubmit();
};
const openLoginModal = async (botInfo: AuthStorageWithBotId | undefined = undefined) => {
loginInfo.value = botInfo;
await nextTick();
console.log('botinfo', botInfo);
loginForm.value?.reset();
loginViewOpen.value = true;
};
defineExpose({
openLoginModal,
});
</script>
<style scoped></style>