Merge pull request 'security-setup' (#3) from security-setup into develop
seems to work in my qubes VM
This commit is contained in:
commit
c2d26108f6
6
.env
6
.env
|
@ -1,5 +1,7 @@
|
|||
COMPOSE_FILE=docker-compose.dev.yml
|
||||
COMPOSE_PROJECT_NAME=candymat
|
||||
|
||||
#Postgraphile vars
|
||||
DATABASE_URL=postgres://candymat:postgres!dev@postgres:5432/candymat_db
|
||||
# Backend vars
|
||||
POSTGRES_PASSWORD=postgres!dev
|
||||
DATABASE_URL=postgres://candymat_postgraphile:postgres!dev@postgres:5432/candymat_db
|
||||
JWT_SECRET=asdfasdfasdf
|
||||
|
|
55
README.md
55
README.md
|
@ -1,5 +1,40 @@
|
|||
# Candymat - Wahl-o-Mat fuer Personalwahlen
|
||||
|
||||
## Introduction
|
||||
|
||||
The candymat is a Wahl-o-Mat for elections of candidates.
|
||||
|
||||
|
||||
## Services
|
||||
|
||||
The project consists of three services:
|
||||
* GraphQL backend (+ postgres)
|
||||
* Redaktions-App
|
||||
* User-App
|
||||
|
||||
### Redaktions-App
|
||||
|
||||
The Redaktions-App is used for editors and candidates to provide questions and answers.
|
||||
|
||||
The app is written with react and appollo-react to access the backend.
|
||||
|
||||
See also: [Service Readme](redaktions-app/README.md)
|
||||
|
||||
### User-App
|
||||
|
||||
The User-App is based on the [EuroMat](https://www.euromat.info/en) (Source: https://github.com/morkro/euromat)
|
||||
and is used to find the perfect candidate for everyone who is allowed to vote.
|
||||
|
||||
It is written in vue.js.
|
||||
|
||||
See also: [Service Readme](https://git.verdigado.com/Netzbegruenung/candymat-user-app/src/README.md)
|
||||
|
||||
### Postgraphile (Backend)
|
||||
|
||||
A package which creates an GraphQL api based on an underlying postgres schema.
|
||||
|
||||
For more on this (e.g. how to use the graphQl api by yourself) see [backend readme](backend/README.md)
|
||||
|
||||
## Development Setup
|
||||
|
||||
### Check-out repository
|
||||
|
@ -12,16 +47,22 @@
|
|||
|
||||
### Start the services
|
||||
|
||||
```docker-compose up -d``` for dev setup.
|
||||
The database will use a volume to persist changes in-between runs.
|
||||
To start with a clean database, either delete the volume from the postgres configuration in the compose file
|
||||
or run ```docker volume rm candymat_db-data``` before starting the containers.
|
||||
|
||||
|
||||
### Where to access the services
|
||||
For dev setup:
|
||||
```
|
||||
docker-compose up
|
||||
```
|
||||
|
||||
* GraphQL IDE/GUI: http://localhost:5433/graphiql
|
||||
* GraphQL Endpoint: http://localhost:5433/graphql
|
||||
* UserApp: http://localhost:8080
|
||||
* RedaktionsApp: http://localhost:8081
|
||||
* Postgres database: http://localhost:5432
|
||||
|
||||
**Note:** The database will use a volume to persist changes in-between runs.
|
||||
To start with a clean database, either delete the volume from the postgres configuration in the compose file
|
||||
or run
|
||||
```
|
||||
docker container rm candymat_postgres_1
|
||||
docker volume rm candymat_db-data
|
||||
```
|
||||
before starting docker-compose.
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
POSTGRES_PASSWORD=postgres!dev
|
||||
POSTGRES_USER=candymat
|
||||
POSTGRES_DB=candymat_db
|
||||
POSTGRES_SCHEMA=candymat_data
|
|
@ -1,6 +0,0 @@
|
|||
FROM postgres:11.5
|
||||
|
||||
COPY ./sql/* /docker-entrypoint-initdb.d/
|
||||
|
||||
RUN localedef -i de_DE -c -f UTF-8 -A /usr/share/locale/locale.alias de_DE.UTF-8
|
||||
ENV LANG de_DE.utf8
|
|
@ -1,11 +1,197 @@
|
|||
# Candymat Backend
|
||||
|
||||
## Setup dev environment
|
||||
The candymat backend consists of a postgres database and a [postgraphile](https://www.graphile.org/postgraphile/introduction/) instance.
|
||||
Postgraphile generates a Graphql backend based on the underlying postgres schema.
|
||||
|
||||
### Postgres via Docker
|
||||
missing
|
||||
## Introduction
|
||||
|
||||
### Postgres on your machine
|
||||
* Install postgres and start it
|
||||
* Create a new database
|
||||
* Execute the scripts in the `./sql` folder in chronological order
|
||||
### Basic structure
|
||||
|
||||
There are three "data" tables:
|
||||
* category
|
||||
* question
|
||||
* answer
|
||||
|
||||
Questions can belong to categories.
|
||||
Answers belong to questions and candidates.
|
||||
|
||||
### User management
|
||||
|
||||
There are four types of roles:
|
||||
* editor
|
||||
* candidate
|
||||
* user
|
||||
* anonymous
|
||||
|
||||
Editors handle questions and categories, candidates handle their specific answers and users are only important
|
||||
in setups where there is no public access to the data.
|
||||
|
||||
#### Authentication
|
||||
|
||||
Authentication is handled via jwt.
|
||||
The candymat setup roughly follows the instructions in the [postgraphile docu](https://www.graphile.org/postgraphile/postgresql-schema-design/#authentication-and-authorization).
|
||||
|
||||
## Manually test the backend
|
||||
|
||||
To test the backend manually an (enhanced) graphiql instance is started in dev mode.
|
||||
To access it navigate to http://localhost:5433/graphiql.
|
||||
|
||||
#### Authenticate (or how to pose as a member of one of the roles)
|
||||
|
||||
To pose as one of the three roles authenticate as
|
||||
* `erika@musterman.de` (editor)
|
||||
* `max@mustermann.de` (candidate)
|
||||
* `happy@user.de` (normal user)
|
||||
|
||||
The password is always "password".
|
||||
|
||||
Use following graphQL mutation to get the jwtToken of an editor:
|
||||
```
|
||||
mutation Authenticate {
|
||||
__typename
|
||||
authenticate(input: {email: "erika@mustermann.de", password: "password"}) {
|
||||
jwtToken
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The jwtToken in the response has to be added to the headers in the following way:
|
||||
```
|
||||
{
|
||||
"Authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiY2FuZHltYXRfY2FuZGlkYXRlIiwicGVyc29uX2lkIjoyLCJleHAiOjE1OTEwNDgzMzgsImlhdCI6MTU5MDg3NTUzNywiYXVkIjoicG9zdGdyYXBoaWxlIiwiaXNzIjoicG9zdGdyYXBoaWxlIn0.21Lu51_suJ5O2RU-UKN2Y6fvKw4SYe-oqx_QqlU0-GE"
|
||||
}
|
||||
```
|
||||
|
||||
#### Query the data tables
|
||||
|
||||
This is possible as member of any role, including no role (anonymous).
|
||||
|
||||
You can use the schema to get familiar with possible queries.
|
||||
As an example, here is a query which retrieves all questions including the category they belong to:
|
||||
```graphql
|
||||
{
|
||||
allQuestions {
|
||||
nodes {
|
||||
categoryByCategoryId {
|
||||
title
|
||||
description
|
||||
}
|
||||
text
|
||||
description
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
Example response:
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"allQuestions": {
|
||||
"nodes": [
|
||||
{
|
||||
"categoryByCategoryId": {
|
||||
"title": "Umwelt",
|
||||
"description": "Themen rund um Naturschutz usw."
|
||||
},
|
||||
"text": "Was sagen Sie zur 10H Regel?",
|
||||
"description": "In Bayern dürfen Windräder nur ..."
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Creating new users
|
||||
|
||||
aka "register"
|
||||
|
||||
Only possible if no bearer token is set in the headers.
|
||||
```graphql
|
||||
mutation Register {
|
||||
registerPerson(input: {firstName: "Ford", lastName: "Prefect", email: "ford@prefect.com", password: "password"}) {
|
||||
person {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Creating questions
|
||||
|
||||
This is only possible as "editor". Use the following mutation:
|
||||
```
|
||||
mutation CreateQuestion($text: String!) {
|
||||
createQuestion(input: {question: {text: $text}}) {
|
||||
question {
|
||||
text
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
with the variables
|
||||
```
|
||||
{
|
||||
"text": "Die Antwort auf die Frage nach dem Leben, dem Universum und dem ganzen Rest?"
|
||||
}
|
||||
```
|
||||
|
||||
#### Creating categories
|
||||
|
||||
This is only possible as "editor".
|
||||
|
||||
Mutation:
|
||||
```
|
||||
mutation CreateCategory($title: String!) {
|
||||
createCategory(input: {category: {title: $title}}) {
|
||||
category {
|
||||
title
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Variables
|
||||
```
|
||||
{
|
||||
"title": "Verkehr"
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
##### Creating answers
|
||||
|
||||
This is only possible as "candidate".
|
||||
Also the `personId` needs to be `2` (the id of Max Mustermann).
|
||||
It is impossible for a candidate to pose as a different candidate when answering a question.
|
||||
|
||||
Mutation:
|
||||
```
|
||||
mutation CreateAnswer($position: Int!, $questionId: Int!, $personId: Int!) {
|
||||
createAnswer(input: {answer: {position: $position, questionId: $questionId, personId: $personId}}) {
|
||||
answer {
|
||||
position
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
Variables
|
||||
```
|
||||
{
|
||||
"questionId": 1,
|
||||
"personId": 2,
|
||||
"position": 2
|
||||
}
|
||||
```
|
||||
Also change the `personId` to see that the candidate can only answer for themself.
|
||||
|
||||
|
||||
##### Updating, Deleting
|
||||
|
||||
Works the same as creating and has the same restrictions for the specific roles.
|
||||
The exact mutations can be inferred looking at the schema definitions.
|
||||
|
|
5
backend/backend.env
Normal file
5
backend/backend.env
Normal file
|
@ -0,0 +1,5 @@
|
|||
# Postgres database setup
|
||||
POSTGRES_USER=candymat_postgraphile
|
||||
# Password is handled by docker-compose
|
||||
POSTGRES_DB=candymat_db
|
||||
POSTGRES_SCHEMA=candymat_data
|
34
backend/security_considerations.md
Normal file
34
backend/security_considerations.md
Normal file
|
@ -0,0 +1,34 @@
|
|||
## Basic security
|
||||
|
||||
Testing the security of the backend is substantial for obvious reasons. Write automated penetration tests.
|
||||
There should be testcases for
|
||||
|
||||
|
||||
| table | editor | candidate | user(v) | user | other |
|
||||
|------------|--------|-----------|---------|------|-------|
|
||||
| person | sdU | sDU | sDU | | |
|
||||
| account | S | S | S | S | | not sure about this
|
||||
| answer | s | sDUI | s | | |
|
||||
| question | sdui | s | s | | |
|
||||
| categories | sdui | s | s | | |
|
||||
|
||||
|
||||
| function | editor | candidate | user(v) | user | other |
|
||||
|--------------|--------|-----------|---------|------|-------|
|
||||
| register | | | | | E |
|
||||
| authenticate | E | E | E | E | |
|
||||
| change pw | E | E | E | | |
|
||||
| change role | e | | | | |
|
||||
|
||||
where
|
||||
* s: select
|
||||
* d: delete
|
||||
* u: update
|
||||
* i: insert
|
||||
* e: execute
|
||||
|
||||
An uppercase version of the above letters means that the operation is only possible on rows directly related to the user id, e.g. a candidate can only delete, update and insert the own answer(s).
|
||||
|
||||
## Passwords
|
||||
DO NOT LOG THE PASSWORDS
|
||||
postgres logging conf may need adoption to NOT log passwords in plain text.
|
|
@ -1,57 +0,0 @@
|
|||
\connect candymat_db
|
||||
|
||||
-- Create schema for candymat_data
|
||||
CREATE SCHEMA candymat_data;
|
||||
|
||||
-- Create table for users
|
||||
CREATE TABLE candymat_data."user"
|
||||
(
|
||||
login character varying(8) primary key,
|
||||
name character varying(300),
|
||||
surname character varying(300),
|
||||
email character varying(320)
|
||||
);
|
||||
|
||||
-- Create table for user groups
|
||||
CREATE TABLE candymat_data."group"
|
||||
(
|
||||
id serial primary key,
|
||||
name character varying(300) UNIQUE,
|
||||
access_right character varying(1000)
|
||||
);
|
||||
|
||||
-- Create table for relation of users and groups
|
||||
CREATE TABLE candymat_data.user_group
|
||||
(
|
||||
group_id integer REFERENCES candymat_data."group" (id) ON UPDATE CASCADE ON DELETE CASCADE,
|
||||
user_login character varying(8) REFERENCES candymat_data."user" (login) ON UPDATE CASCADE ON DELETE CASCADE,
|
||||
primary key (group_id, user_login)
|
||||
);
|
||||
|
||||
|
||||
-- Create table for categories
|
||||
CREATE TABLE candymat_data.category
|
||||
(
|
||||
id serial primary key,
|
||||
title character varying(300) UNIQUE NOT NULL,
|
||||
description character varying(5000)
|
||||
);
|
||||
|
||||
-- Create table for questions
|
||||
CREATE TABLE candymat_data.question
|
||||
(
|
||||
id serial primary key,
|
||||
category_id integer REFERENCES candymat_data."category" (id) ON UPDATE CASCADE ON DELETE SET NULL,
|
||||
text character varying(3000) NOT NULL,
|
||||
description character varying(5000)
|
||||
);
|
||||
|
||||
-- Create table for answers
|
||||
CREATE TABLE candymat_data.answer
|
||||
(
|
||||
question_id integer REFERENCES candymat_data."question" (id) ON UPDATE CASCADE ON DELETE CASCADE,
|
||||
user_login character varying(8) REFERENCES candymat_data."user" ON UPDATE CASCADE ON DELETE CASCADE,
|
||||
position integer NOT NULL,
|
||||
text character varying(5000),
|
||||
primary key (question_id, user_login)
|
||||
);
|
28
backend/sql/01_create_basic_structure.sql
Normal file
28
backend/sql/01_create_basic_structure.sql
Normal file
|
@ -0,0 +1,28 @@
|
|||
\connect candymat_db
|
||||
|
||||
-- Create schema for candymat_data
|
||||
create SCHEMA candymat_data;
|
||||
create SCHEMA candymat_data_privat;
|
||||
|
||||
-- create roles
|
||||
create role candymat_person;
|
||||
create role candymat_anonymous;
|
||||
create role candymat_editor;
|
||||
create role candymat_candidate;
|
||||
|
||||
grant candymat_editor to candymat_postgraphile;
|
||||
grant candymat_candidate to candymat_postgraphile;
|
||||
grant candymat_person to candymat_postgraphile, candymat_candidate, candymat_editor;
|
||||
grant candymat_anonymous to candymat_postgraphile;
|
||||
|
||||
create type candymat_data.role as enum (
|
||||
'candymat_editor',
|
||||
'candymat_candidate',
|
||||
'candymat_person'
|
||||
);
|
||||
|
||||
-- set table wide permissions
|
||||
grant usage on schema candymat_data to candymat_anonymous, candymat_person;
|
||||
|
||||
-- make functions non executeable w/o further checks
|
||||
alter default privileges revoke execute on functions from public;
|
34
backend/sql/02_create-user_tables.sql
Normal file
34
backend/sql/02_create-user_tables.sql
Normal file
|
@ -0,0 +1,34 @@
|
|||
-- create table for users
|
||||
create table candymat_data.person
|
||||
(
|
||||
id serial primary key,
|
||||
first_name character varying(200),
|
||||
last_name character varying(200),
|
||||
about character varying(2000),
|
||||
created_at timestamp default now(),
|
||||
role candymat_data.role not null default 'candymat_person'
|
||||
);
|
||||
grant select, update, delete on table candymat_data.person to candymat_person;
|
||||
-- the following is only necessary as long as anonymous should be able to view candidates and editors
|
||||
grant select on table candymat_data.person to candymat_anonymous;
|
||||
|
||||
-- create table for accounts
|
||||
create table candymat_data_privat.person_account
|
||||
(
|
||||
person_id integer primary key references candymat_data.person (id) on delete cascade,
|
||||
email character varying(320) not null unique check (email ~* '^.+@.+\..+$'),
|
||||
password_hash character varying(256) not null
|
||||
);
|
||||
alter table candymat_data.person
|
||||
enable row level security;
|
||||
create policy update_person on candymat_data.person for update to candymat_person
|
||||
with check (id = nullif(current_setting('jwt.claims.person_id', true), '')::integer);
|
||||
create policy delete_person on candymat_data.person for delete to candymat_person
|
||||
using (id = nullif(current_setting('jwt.claims.person_id', true), '')::integer);
|
||||
-- The following enables viewing candidates and editors information for every person.
|
||||
-- This may be changed to only enable registered (and verified) persons.
|
||||
create policy select_person_public
|
||||
on candymat_data.person
|
||||
for select
|
||||
to candymat_anonymous, candymat_person -- maybe change to candymat_person only in the future
|
||||
using (role in ('candymat_editor', 'candymat_candidate'));
|
51
backend/sql/03_create_content_tables.sql
Normal file
51
backend/sql/03_create_content_tables.sql
Normal file
|
@ -0,0 +1,51 @@
|
|||
-- create table for categories
|
||||
create table candymat_data.category
|
||||
(
|
||||
id serial primary key,
|
||||
title character varying(300) UNIQUE NOT NULL,
|
||||
description character varying(5000)
|
||||
);
|
||||
grant select on table candymat_data.category to candymat_person;
|
||||
-- the following line is only necessary as long as the candymat should be publicly accessible
|
||||
grant select on table candymat_data.category to candymat_anonymous;
|
||||
grant insert, update, delete on table candymat_data.category to candymat_editor;
|
||||
grant usage on sequence candymat_data.category_id_seq to candymat_editor;
|
||||
|
||||
-- create table for questions
|
||||
create table candymat_data.question
|
||||
(
|
||||
id serial primary key,
|
||||
category_id integer REFERENCES candymat_data.category (id) ON UPDATE CASCADE ON DELETE SET NULL,
|
||||
text character varying(3000) NOT NULL,
|
||||
description character varying(5000)
|
||||
);
|
||||
grant select on table candymat_data.question to candymat_person;
|
||||
-- the following line is only necessary as long as the candymat should be publicly accessible
|
||||
grant select on table candymat_data.question to candymat_anonymous;
|
||||
grant insert, update, delete on table candymat_data.question to candymat_editor;
|
||||
grant usage on sequence candymat_data.question_id_seq to candymat_editor;
|
||||
|
||||
-- create table for answers
|
||||
create table candymat_data.answer
|
||||
(
|
||||
question_id integer REFERENCES candymat_data.question (id) ON UPDATE CASCADE ON DELETE CASCADE,
|
||||
person_id integer REFERENCES candymat_data.person (id) ON UPDATE CASCADE ON DELETE CASCADE,
|
||||
position integer NOT NULL,
|
||||
text character varying(5000),
|
||||
created_at timestamp default now(),
|
||||
primary key (question_id, person_id)
|
||||
);
|
||||
grant select on table candymat_data.answer to candymat_person;
|
||||
-- the following line is only necessary as long as the candymat should be publicly accessible
|
||||
grant select on table candymat_data.answer to candymat_anonymous;
|
||||
grant insert, update, delete on table candymat_data.answer to candymat_candidate;
|
||||
|
||||
alter table candymat_data.answer
|
||||
enable row level security;
|
||||
create policy change_answer on candymat_data.answer to candymat_candidate
|
||||
using (person_id = nullif(current_setting('jwt.claims.person_id', true), '')::integer);
|
||||
create policy select_answer
|
||||
on candymat_data.answer
|
||||
for select
|
||||
to candymat_anonymous, candymat_person -- maybe change to candymat_person only in the future
|
||||
using (true);
|
80
backend/sql/04_setup_authentication.sql
Normal file
80
backend/sql/04_setup_authentication.sql
Normal file
|
@ -0,0 +1,80 @@
|
|||
create extension if not exists "pgcrypto";
|
||||
|
||||
-- Define JWT claim structure
|
||||
create type candymat_data.jwt_token as (
|
||||
role text,
|
||||
person_id integer,
|
||||
exp bigint
|
||||
);
|
||||
|
||||
create function candymat_data.current_person() returns candymat_data.person as $$
|
||||
select *
|
||||
from candymat_data.person
|
||||
where id = nullif(current_setting('jwt.claims.person_id', true), '')::integer
|
||||
$$ language sql stable;
|
||||
grant execute on function candymat_data.current_person() to candymat_person;
|
||||
|
||||
create function candymat_data.register_person(
|
||||
first_name text,
|
||||
last_name text,
|
||||
email text,
|
||||
password text
|
||||
) returns candymat_data.person as $$
|
||||
declare
|
||||
person candymat_data.person;
|
||||
begin
|
||||
insert into candymat_data.person (first_name, last_name)
|
||||
values ($1, $2)
|
||||
returning * into person;
|
||||
|
||||
insert into candymat_data_privat.person_account (person_id, email, password_hash)
|
||||
values (person.id, $3, crypt($4, gen_salt('bf')));
|
||||
|
||||
return person;
|
||||
end;
|
||||
$$ language plpgsql strict security definer;
|
||||
grant execute on function candymat_data.register_person(text, text, text, text) to candymat_anonymous;
|
||||
|
||||
create function candymat_data.authenticate(
|
||||
email text,
|
||||
password text
|
||||
) returns candymat_data.jwt_token as $$
|
||||
declare
|
||||
account candymat_data_privat.person_account;
|
||||
declare person candymat_data.person;
|
||||
begin
|
||||
select a.*
|
||||
into account
|
||||
from candymat_data_privat.person_account as a
|
||||
where a.email = $1;
|
||||
|
||||
select p.*
|
||||
into person
|
||||
from candymat_data.person as p
|
||||
where p.id = account.person_id;
|
||||
|
||||
if account.password_hash = crypt(password, account.password_hash) then
|
||||
return (person.role, account.person_id,
|
||||
extract(epoch from (now() + interval '2 days')))::candymat_data.jwt_token;
|
||||
else
|
||||
return null;
|
||||
end if;
|
||||
end;
|
||||
$$ language plpgsql strict security definer;
|
||||
grant execute on function candymat_data.authenticate(text, text) to candymat_anonymous, candymat_person;
|
||||
|
||||
create function candymat_data.change_role(
|
||||
person_id integer,
|
||||
new_role candymat_data.role
|
||||
) returns table(first_name text, last_name text, role candymat_data.role) as $$
|
||||
begin
|
||||
update candymat_data.person
|
||||
set role = new_role
|
||||
where candymat_data.person.id = $1;
|
||||
|
||||
return query select candymat_data.person.first_name::text, candymat_data.person.last_name::text, new_role
|
||||
from candymat_data.person
|
||||
where person.id = person_id;
|
||||
end;
|
||||
$$ language plpgsql;
|
||||
grant execute on function candymat_data.change_role(integer, candymat_data.role) to candymat_editor;
|
30
backend/sql/test_01_add_users.sql
Normal file
30
backend/sql/test_01_add_users.sql
Normal file
|
@ -0,0 +1,30 @@
|
|||
select candymat_data.register_person(
|
||||
'Erika',
|
||||
'Mustermann',
|
||||
'erika@mustermann.de',
|
||||
'password'
|
||||
);
|
||||
select candymat_data.change_role(
|
||||
1,
|
||||
'candymat_editor'
|
||||
);
|
||||
select candymat_data.register_person(
|
||||
'Max',
|
||||
'Mustermann',
|
||||
'max@mustermann.de',
|
||||
'password'
|
||||
);
|
||||
select candymat_data.change_role(
|
||||
2,
|
||||
'candymat_candidate'
|
||||
);
|
||||
select candymat_data.register_person(
|
||||
'Happy',
|
||||
'User',
|
||||
'happy@user.de',
|
||||
'password'
|
||||
);
|
||||
select candymat_data.change_role(
|
||||
3,
|
||||
'candymat_person'
|
||||
);
|
5
backend/sql/test_02_add_questions.sql
Normal file
5
backend/sql/test_02_add_questions.sql
Normal file
|
@ -0,0 +1,5 @@
|
|||
insert into candymat_data.category (title, description) values
|
||||
('Umwelt', 'Themen rund um Naturschutz usw.');
|
||||
|
||||
insert into candymat_data.question (category_id, text, description) values
|
||||
(1, 'Was sagen Sie zur 10H Regel?', 'In Bayern dürfen Windräder nur ...');
|
|
@ -28,7 +28,9 @@ services:
|
|||
build:
|
||||
dockerfile: ./Dockerfile
|
||||
context: ./backend/
|
||||
env_file: ./backend/.env-backend
|
||||
environment:
|
||||
- "POSTGRES_PASSWORD=${POSTGRES_PASSWORD}"
|
||||
env_file: ./backend/backend.env
|
||||
ports:
|
||||
- "5432:5432"
|
||||
restart: always
|
||||
|
@ -42,9 +44,21 @@ services:
|
|||
image: graphile/postgraphile
|
||||
depends_on:
|
||||
- postgres
|
||||
env_file: ./backend/backend.env
|
||||
ports:
|
||||
- "5433:5000"
|
||||
command: ["--connection", $DATABASE_URL, "--host", "0.0.0.0", "--port", "5000", "--schema", "candymat_data", "--watch"]
|
||||
command: [
|
||||
"--connection", $DATABASE_URL,
|
||||
"--host", "0.0.0.0",
|
||||
"--port", "5000",
|
||||
"--schema", "candymat_data",
|
||||
"--default-role", "candymat_anonymous",
|
||||
"--jwt-token-identifier", "candymat_data.jwt_token",
|
||||
"--jwt-secret", $JWT_SECRET,
|
||||
"--watch",
|
||||
"--retry-on-init-fail",
|
||||
"--enhance-graphiql"
|
||||
]
|
||||
networks:
|
||||
- frontend
|
||||
- backend
|
||||
|
|
|
@ -6,7 +6,7 @@ import { CustomAppBar } from 'components/CustomAppBar/CustomAppBar';
|
|||
import { Overview } from 'components/Overview/Overview';
|
||||
|
||||
|
||||
export const dataApi = '/api'
|
||||
export const dataApi = 'http://localhost:5000/graphql'
|
||||
|
||||
|
||||
const styles = createStyles({
|
||||
|
|
Loading…
Reference in a new issue