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

View File

@ -7,7 +7,9 @@
:key="bot.botId" :key="bot.botId"
:active="bot.botId === botStore.selectedBot" :active="bot.botId === botStore.selectedBot"
button 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)" @click="botStore.selectBot(bot.botId)"
> >
<bot-rename <bot-rename
@ -17,10 +19,16 @@
@cancelled="stopEditBot(bot.botId)" @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-item>
</b-list-group> </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> </div>
</template> </template>
@ -31,6 +39,7 @@ import BotRename from '@/components/BotRename.vue';
import { defineComponent, ref } from 'vue'; import { defineComponent, ref } from 'vue';
import { useBotStore } from '@/stores/ftbotwrapper'; import { useBotStore } from '@/stores/ftbotwrapper';
import { AuthStorageWithBotId } from '@/types';
export default defineComponent({ export default defineComponent({
name: 'BotList', name: 'BotList',
@ -42,6 +51,7 @@ export default defineComponent({
const botStore = useBotStore(); const botStore = useBotStore();
const editingBots = ref<string[]>([]); const editingBots = ref<string[]>([]);
const loginModal = ref<typeof LoginModal>();
const editBot = (botId: string) => { const editBot = (botId: string) => {
if (!editingBots.value.includes(botId)) { 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) => { const stopEditBot = (botId: string) => {
if (!editingBots.value.includes(botId)) { if (!editingBots.value.includes(botId)) {
return; return;
@ -61,7 +79,9 @@ export default defineComponent({
botStore, botStore,
editingBots, editingBots,
editBot, editBot,
editBotLogin,
stopEditBot, stopEditBot,
loginModal,
}; };
}, },
}); });

View File

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

View File

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

View File

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

View File

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