Add error screen

This commit is contained in:
Dominik Korsa 2021-01-22 17:47:26 +01:00
parent 800e545ff5
commit 3c1f07f3b0
No known key found for this signature in database
GPG key ID: 546F986F71A6FE6E
7 changed files with 217 additions and 107 deletions

View file

@ -18,17 +18,28 @@ export default function registerAuthorize(server: MyFastifyInstance): void {
) => {
if (!isObject(request.query)) {
server.log.warn('Request query is not an object');
throw server.httpErrors.badRequest();
await reply.redirect(urlJoin(
websitePrefix,
`/prompt-error?code=invalid_request&description=${
encodeURIComponent('Request query is not an object')
}`,
));
return;
}
try {
validateParam('client_id', request.query.client_id);
validateParam('redirect_uri', request.query.redirect_uri);
} catch (error) {
if (error instanceof ParamError) {
throw server.httpErrors.badRequest(error.message);
await reply.redirect(urlJoin(
websitePrefix,
`/prompt-error?code=invalid_request&description=${encodeURIComponent(error.message)}`,
));
return;
}
server.log.error(error);
throw server.httpErrors.internalServerError();
await reply.redirect(urlJoin(websitePrefix, '/prompt-error?code=internal'));
return;
}
const application = await database.applicationRepo.findOne({
@ -36,8 +47,14 @@ export default function registerAuthorize(server: MyFastifyInstance): void {
clientId: request.query.client_id,
},
});
if (application === undefined) throw server.httpErrors.badRequest('Unknown application');
if (!application.redirectUris.includes(request.query.redirect_uri)) throw server.httpErrors.badRequest('Redirect URI not registered');
if (application === undefined) {
await reply.redirect(urlJoin(websitePrefix, '/prompt-error?code=unknown_application'));
return;
}
if (!application.redirectUris.includes(request.query.redirect_uri)) {
await reply.redirect(urlJoin(websitePrefix, '/prompt-error?code=unknown_redirect_uri'));
return;
}
try {
validateParam('response_type', request.query.response_type);

View file

@ -23,5 +23,11 @@ module.exports = {
'max-len': ['off']
},
},
{
files: '**/*.vue',
rules: {
'class-methods-use-this': ['off']
}
}
],
};

View file

@ -0,0 +1,35 @@
<template>
<v-app class="dialog-app">
<div>
<div class="text-h4 text-center mt-8 mx-2">
<span class="primary--text">Wulkanowy</span> Bridge
</div>
</div>
<v-main class="px-4">
<v-sheet max-width="500" class="mx-auto mt-16" color="transparent">
<slot />
</v-sheet>
</v-main>
</v-app>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
@Component({
name: 'DialogApp',
})
export default class DialogApp extends Vue {
}
</script>
<style lang="scss">
.dialog-app {
background-color: #f7f7f7 !important;
.v-card__text, .v-card__title {
word-break: normal;
}
}
</style>

View file

@ -1,107 +1,96 @@
<template>
<v-app class="authenticate-prompt-app">
<div>
<div class="text-h4 text-center mt-8">
<span class="primary--text">Wulkanowy</span> Bridge
</div>
<dialog-app class="authenticate-prompt-app">
<v-alert type="error" text v-if="!promptId">
Brak wymaganego parametru <code>prompt_id</code>
</v-alert>
<v-alert type="error" text v-else-if="promptInfoError">
Nie udało się wczytać danych
<template #append>
<v-btn text color="error" @click="loadPromptInfo">
Spróbuj ponownie
</v-btn>
</template>
</v-alert>
<div class="d-flex align-center justify-center mt-16 mb-16" v-else-if="!promptInfo">
<v-progress-circular indeterminate :size="96" color="primary" />
</div>
<v-main class="px-4">
<v-sheet max-width="500" class="mx-auto mt-16" color="transparent">
<v-alert type="error" text v-if="!promptId">
Brak wymaganego parametru <code>prompt_id</code>
</v-alert>
<v-alert type="error" text v-else-if="promptInfoError">
Nie udało się wczytać danych
<template #append>
<v-btn text color="error" @click="loadPromptInfo">
Spróbuj ponownie
</v-btn>
</template>
</v-alert>
<div class="d-flex align-center justify-center mt-16 mb-16" v-else-if="!promptInfo">
<v-progress-circular indeterminate :size="96" color="primary" />
</div>
<template v-else>
<div class="pb-1 text--secondary">
Krok <span class="primary--text">{{ step }}/3</span>
</div>
<v-card outlined>
<div class="d-flex justify-center mn-16 avatar__wrapper">
<v-badge
:color="promptInfo.application.verified ? 'green' : 'grey'"
offset-x="64"
offset-y="16"
bottom
:content="promptInfo.application.verified ? 'Zweryfikowana' : 'Niezweryfikowana'"
:value="step === 1"
<template v-else>
<div class="pb-1 text--secondary">
Krok <span class="primary--text">{{ step }}/3</span>
</div>
<v-card outlined>
<div class="d-flex justify-center mn-16 avatar__wrapper">
<v-badge
:color="promptInfo.application.verified ? 'green' : 'grey'"
offset-x="64"
offset-y="16"
bottom
:content="promptInfo.application.verified ? 'Zweryfikowana' : 'Niezweryfikowana'"
:value="step === 1"
>
<transition name="scale">
<v-sheet
v-if="step === 1"
width="128"
height="128"
class="avatar-sheet mx-4 overflow-hidden"
outlined
>
<transition name="scale">
<v-sheet
v-if="step === 1"
width="128"
height="128"
class="avatar-sheet mx-4 overflow-hidden"
outlined
<v-sheet
class="fill-height d-flex align-center justify-center"
:color="promptInfo.application.iconColor"
>
<v-img
:src="promptInfo.application.iconUrl"
width="80"
height="80"
aspect-ratio="1"
contain
>
<v-sheet
class="fill-height d-flex align-center justify-center"
:color="promptInfo.application.iconColor"
>
<v-img
:src="promptInfo.application.iconUrl"
width="80"
height="80"
aspect-ratio="1"
contain
>
<template v-slot:placeholder>
<div class="fill-height d-flex align-center justify-center">
<v-icon :size="80">
mdi-help
</v-icon>
</div>
</template>
</v-img>
</v-sheet>
</v-sheet>
</transition>
</v-badge>
</div>
<v-window :value="step">
<v-window-item :value="1">
<overview-window
:promptInfo="promptInfo"
@next="toLoginWindow"
/>
</v-window-item>
<v-window-item :value="2" eager>
<login-window
ref="loginWindow"
:prompt-info="promptInfo"
@login="login"
@back="loginBack"
/>
</v-window-item>
<v-window-item :value="3">
<students-window
v-if="students !== null"
:prompt-info="promptInfo"
:students="students"
@back="toLoginWindow"
/>
</v-window-item>
</v-window>
</v-card>
</template>
</v-sheet>
</v-main>
</v-app>
<template v-slot:placeholder>
<div class="fill-height d-flex align-center justify-center">
<v-icon :size="80">
mdi-help
</v-icon>
</div>
</template>
</v-img>
</v-sheet>
</v-sheet>
</transition>
</v-badge>
</div>
<v-window :value="step">
<v-window-item :value="1">
<overview-window
:promptInfo="promptInfo"
@next="toLoginWindow"
/>
</v-window-item>
<v-window-item :value="2" eager>
<login-window
ref="loginWindow"
:prompt-info="promptInfo"
@login="login"
@back="loginBack"
/>
</v-window-item>
<v-window-item :value="3">
<students-window
v-if="students !== null"
:prompt-info="promptInfo"
:students="students"
@back="toLoginWindow"
/>
</v-window-item>
</v-window>
</v-card>
</template>
</dialog-app>
</template>
<style lang="scss">
.authenticate-prompt-app {
background-color: #f7f7f7 !important;
.avatar-sheet {
border-radius: 50%;
}
@ -122,10 +111,6 @@
.scale-enter, .scale-leave-to {
transform: scale(0);
}
.v-card__text, .v-card__title {
word-break: normal;
}
}
</style>
@ -136,10 +121,13 @@ import { PromptInfo, Student } from '@/types';
import LoginWindow from '@/compontents/authenticate-prompt-windows/login-window.vue';
import StudentsWindow from '@/compontents/authenticate-prompt-windows/students-window.vue';
import { sdk } from '@/pages/authenticate-prompt/sdk';
import DialogApp from '@/compontents/dialog-app.vue';
@Component({
name: 'AuthenticatePromptApp',
components: { LoginWindow, OverviewWindow, StudentsWindow },
components: {
LoginWindow, OverviewWindow, StudentsWindow, DialogApp,
},
})
export default class AuthenticatePromptApp extends Vue {
@Ref() readonly loginWindow!: LoginWindow

View file

@ -0,0 +1,48 @@
<template>
<dialog-app>
<v-card outlined>
<v-card-title>Podczas autoryzacji wystąpił błąd</v-card-title>
<v-card-text>
<v-alert text type="error">
{{ errorMessage }}<br>
<code
v-if="errorDescription !== null"
class="d-block mt-3 py-2 px-sm-3"
>
{{ errorDescription }}
</code>
</v-alert>
Skontaktuj się z twórcą aplikacji w celu rozwiązania problemu
</v-card-text>
</v-card>
</dialog-app>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import DialogApp from '@/compontents/dialog-app.vue';
@Component({
name: 'PromptErrorApp',
components: { DialogApp },
})
export default class PromptErrorApp extends Vue {
errorCode: string | null = null;
errorDescription: string | null = null;
get errorMessage() {
if (this.errorCode === 'invalid_request') return 'Niepoprawny format zapytania';
if (this.errorCode === 'internal') return 'Błąd serwera';
if (this.errorCode === 'unknown_application') return 'Nie znaleziono aplikacji';
if (this.errorCode === 'unknown_redirect_uri') return 'Niepoprawne URI przekierowania';
return 'Nieznany błąd';
}
async created() {
const searchParams = new URLSearchParams(window.location.search);
this.errorCode = searchParams.get('code');
this.errorDescription = searchParams.get('description');
}
}
</script>

View file

@ -0,0 +1,10 @@
import Vue from 'vue';
import vuetify from '@/plugins/vuetify';
import AuthenticatePromptApp from './app.vue';
Vue.config.productionTip = false;
new Vue({
vuetify,
render: (h) => h(AuthenticatePromptApp),
}).$mount('#app');

View file

@ -7,7 +7,13 @@ module.exports = {
entry: 'src/pages/authenticate-prompt/main.ts',
template: 'public/index.html',
filename: 'authenticate-prompt.html',
title: 'Authorize application | Wulkanowy Bridge',
title: 'Autoryzuj aplikację | Wulkanowy Bridge',
},
'prompt-error': {
entry: 'src/pages/prompt-error/main.ts',
template: 'public/index.html',
filename: 'prompt-error.html',
title: 'Błąd autoryzacji | Wulkanowy Bridge',
},
},
};