Implement /api/website/deny

This commit is contained in:
Dominik Korsa 2021-01-18 21:05:04 +01:00
parent a4419528a0
commit c4d3c4d003
No known key found for this signature in database
GPG key ID: 546F986F71A6FE6E
9 changed files with 107 additions and 8 deletions

View file

@ -2,5 +2,6 @@
<project version="4">
<component name="JavaScriptLibraryMappings">
<file url="file://$PROJECT_DIR$" libraries="{Node.js Core}" />
<file url="file://$PROJECT_DIR$/website" libraries="{@mdi/font}" />
</component>
</project>

View file

@ -427,6 +427,11 @@
"integrity": "sha512-7NQmHra/JILCd1QqpSzl8+mJRc8ZHz3uDm8YV1Ks9IhK0epEiTw8aIErbvH9PI+6XbqhyIQy3462nEsn7UVzjQ==",
"dev": true
},
"@types/url-join": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/@types/url-join/-/url-join-4.0.0.tgz",
"integrity": "sha512-awrJu8yML4E/xTwr2EMatC+HBnHGoDxc2+ImA9QyeUELI1S7dOCIZcyjki1rkwoA8P2D2NVgLAJLjnclkdLtAw=="
},
"@types/ws": {
"version": "7.4.0",
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-7.4.0.tgz",
@ -2076,11 +2081,41 @@
"resolved": "https://registry.npmjs.org/fastify-error/-/fastify-error-0.3.0.tgz",
"integrity": "sha512-Jm2LMTB5rsJqlS1+cmgqqM9tTs0UrlgYR7TvDT3ZgXsUI5ib1NjQlqZHf+tDK5tVPdFGwyq02wAoJtyYIRSiFA=="
},
"fastify-http-proxy": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/fastify-http-proxy/-/fastify-http-proxy-4.2.0.tgz",
"integrity": "sha512-qxIj5AHrt4sgTVggv1km+dr1105gzHRk39/rhzuNIUy747m2WswsLM+ZeedulK/EGYSTNluKwNhbqwPSZ4JvnA==",
"requires": {
"fastify-reply-from": "^3.1.3",
"ws": "^7.4.1"
},
"dependencies": {
"ws": {
"version": "7.4.2",
"resolved": "https://registry.npmjs.org/ws/-/ws-7.4.2.tgz",
"integrity": "sha512-T4tewALS3+qsrpGI/8dqNMLIVdq/g/85U98HPMa6F0m6xTbvhXU6RCQLqPH3+SlomNV/LdY6RXEbBpMH6EOJnA=="
}
}
},
"fastify-plugin": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/fastify-plugin/-/fastify-plugin-3.0.0.tgz",
"integrity": "sha512-ZdCvKEEd92DNLps5n0v231Bha8bkz1DjnPP/aEz37rz/q42Z5JVLmgnqR4DYuNn3NXAO3IDCPyRvgvxtJ4Ym4w=="
},
"fastify-reply-from": {
"version": "3.5.0",
"resolved": "https://registry.npmjs.org/fastify-reply-from/-/fastify-reply-from-3.5.0.tgz",
"integrity": "sha512-kNc0taosEyZz1DZBo/Dt4ihxF210gqalhRAiKFKT0VvGCL/+3A1JunlY/ExeoJUyfiCUWpRJ87pj4FCdrhbZLA==",
"requires": {
"end-of-stream": "^1.4.1",
"fastify-plugin": "^3.0.0",
"http-errors": "^1.8.0",
"pump": "^3.0.0",
"semver": "^7.2.1",
"tiny-lru": "^7.0.0",
"undici": "^2.1.0"
}
},
"fastify-sensible": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/fastify-sensible/-/fastify-sensible-3.1.0.tgz",
@ -4118,6 +4153,11 @@
"random-bytes": "~1.0.0"
}
},
"undici": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/undici/-/undici-2.2.1.tgz",
"integrity": "sha512-21sJmMvJOMsyt/2pQPgB5Ruvm2ADTTm34NHRy4kzfeW9uMO7gK2oN0f+5KaJCmoKGJb8KxdU6yWpW0SphFHadw=="
},
"universalify": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
@ -4131,6 +4171,11 @@
"punycode": "^2.1.0"
}
},
"url-join": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz",
"integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA=="
},
"util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",

View file

@ -17,11 +17,13 @@
"@types/express": "^4.17.10",
"@types/lodash": "^4.14.167",
"@types/node": "^14.14.21",
"@types/url-join": "^4.0.0",
"@wulkanowy/sdk": "^0.1.1",
"apollo-server-fastify": "^2.19.2",
"dotenv": "^8.2.0",
"fastify": "^3.10.1",
"fastify-cookie": "^5.1.0",
"fastify-http-proxy": "^4.2.0",
"fastify-sensible": "^3.1.0",
"fastify-session": "^5.2.1",
"lodash": "^4.17.20",
@ -32,7 +34,8 @@
"ts-node": "^9.1.1",
"type-graphql": "^1.1.1",
"typeorm": "^0.2.30",
"typescript": "^4.1.3"
"typescript": "^4.1.3",
"url-join": "^4.0.1"
},
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^4.13.0",

View file

@ -4,3 +4,5 @@ export const scopes = [
'notes',
'achievements',
];
export const websitePrefix = '/';

View file

@ -5,8 +5,10 @@ dotenv.config();
import Fastify from 'fastify';
import FastifyCookie from 'fastify-cookie';
import FastifyHttpProxy from 'fastify-http-proxy';
import FastifySensible from 'fastify-sensible';
import FastifySession from 'fastify-session';
import { websitePrefix } from './constants';
import database from './database/database';
import registerOAuth from './routes/oauth2';
import registerWebsiteApi from './routes/website-api';
@ -28,6 +30,15 @@ async function start() {
secure: false, // TODO: Remove this line or add development env variable
},
});
const websiteProxyUpstream = process.env.PROXY_WEBSITE;
if (websiteProxyUpstream !== undefined) {
await server.register(FastifyHttpProxy, {
upstream: websiteProxyUpstream,
prefix: websitePrefix,
});
}
await server.register(registerOAuth, { prefix: '/api/oauth', logLevel: 'info' });
await server.register(registerWebsiteApi, { prefix: '/api/website', logLevel: 'info' });

View file

@ -1,6 +1,7 @@
import _ from 'lodash';
import { nanoid } from 'nanoid';
import { scopes } from '../../constants';
import urlJoin from 'url-join';
import { scopes, websitePrefix } from '../../constants';
import database from '../../database/database';
import { ParamError, ScopeError } from '../../errors';
import type { MyFastifyInstance, StudentsMode } from '../../types';
@ -10,7 +11,7 @@ import {
} from '../../utils';
export default function registerAuthorize(server: MyFastifyInstance): void {
server.post('/authorize', async (
server.get('/authorize', async (
request,
reply,
) => {
@ -80,7 +81,7 @@ export default function registerAuthorize(server: MyFastifyInstance): void {
studentsMode,
});
await reply.redirect(`/authenticate-prompt?prompt_id=${promptId}`);
await reply.redirect(urlJoin(websitePrefix, `/authenticate-prompt?prompt_id=${promptId}`));
return;
}
await reply.redirect(`${request.query.redirect_uri}?error=unsupported_response_type`);

View file

@ -1,7 +1,8 @@
import { ApolloServer } from 'apollo-server-fastify';
import { buildSchema } from 'type-graphql';
import { ParamError } from '../../errors';
import type { ApolloContext, MyFastifyInstance } from '../../types';
import { getSessionData } from '../../utils';
import { getSessionData, isObject, validateParam } from '../../utils';
import LoginResolver from './resolvers/login-resolver';
import PromptInfoResolver from './resolvers/prompt-info-resolver';
import type { WebsiteAPIContext } from './types';
@ -26,5 +27,26 @@ export default async function registerWebsiteApi(server: MyFastifyInstance): Pro
origin: false,
},
}));
console.log(apolloServer.graphqlPath);
server.get('/deny', async (
request,
reply,
) => {
if (!isObject(request.query)) {
server.log.warn('Request query is not an object');
throw server.httpErrors.badRequest();
}
try {
validateParam('prompt_id', request.query.prompt_id);
} catch (error) {
if (error instanceof ParamError) {
throw server.httpErrors.badRequest(error.message);
}
server.log.error(error);
throw server.httpErrors.internalServerError();
}
const prompt = getSessionData(request.session).prompts.get(request.query.prompt_id);
if (!prompt) throw server.httpErrors.badRequest('Prompt data not found');
await reply.redirect(`${prompt.redirectUri}?error=access_denied&error_description=${encodeURIComponent('User denied')}`);
});
}

View file

@ -7,7 +7,10 @@
</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="promptInfoError">
<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">
@ -97,7 +100,7 @@
<v-divider />
</div>
<v-card-actions>
<v-btn color="primary" text outlined>
<v-btn color="primary" text outlined :href="denyUrl">
Odmów
</v-btn>
<v-spacer />
@ -165,6 +168,8 @@ export interface PromptInfo {
export default class AuthenticatePromptApp extends Vue {
promptInfo: PromptInfo | null = null;
promptId: string | null = null;
promptInfoError = false;
step = 1;
@ -201,6 +206,11 @@ export default class AuthenticatePromptApp extends Vue {
}));
}
get denyUrl() {
if (!this.promptId) return undefined;
return `/api/website/deny?prompt_id=${this.promptId}`;
}
async loadPromptInfo() {
this.promptInfoError = false;
this.promptInfo = null;
@ -222,6 +232,9 @@ export default class AuthenticatePromptApp extends Vue {
}
async created() {
const searchParams = new URLSearchParams(window.location.search);
this.promptId = searchParams.get('prompt_id');
if (!this.promptId) return;
await this.loadPromptInfo();
}
}

View file

@ -5,5 +5,6 @@
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="@mdi/font" level="application" />
</component>
</module>