├── .gitignore
├── flask-spa-jinja
├── .gitignore
├── requirements.txt
├── public
│ ├── favicon.png
│ └── global.css
├── src
│ ├── main.js
│ └── App.svelte
├── README.md
├── package.json
├── templates
│ └── index.html
├── rollup.config.js
├── app.py
├── scripts
│ └── setupTypeScript.js
└── package-lock.json
├── flask-spa-same-origin
├── frontend
│ ├── .gitignore
│ ├── public
│ │ ├── favicon.png
│ │ ├── index.html
│ │ └── global.css
│ ├── src
│ │ ├── main.js
│ │ └── App.svelte
│ ├── Dockerfile
│ ├── package.json
│ ├── rollup.config.js
│ └── scripts
│ │ └── setupTypeScript.js
├── backend
│ ├── requirements.txt
│ ├── Dockerfile
│ └── app.py
├── nginx
│ ├── Dockerfile
│ └── nginx.conf
├── README.md
└── docker-compose.yml
├── flask-spa-cross-origin
├── frontend
│ ├── .gitignore
│ ├── public
│ │ ├── favicon.png
│ │ ├── index.html
│ │ └── global.css
│ ├── src
│ │ ├── main.js
│ │ └── App.svelte
│ ├── package.json
│ ├── rollup.config.js
│ ├── scripts
│ │ └── setupTypeScript.js
│ └── package-lock.json
├── backend
│ ├── requirements.txt
│ └── app.py
└── README.md
├── README.md
└── LICENSE
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | env
3 | build
4 |
--------------------------------------------------------------------------------
/flask-spa-jinja/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules/
2 | /public/build/
3 |
4 | .DS_Store
5 |
--------------------------------------------------------------------------------
/flask-spa-jinja/requirements.txt:
--------------------------------------------------------------------------------
1 | Flask==2.2.2
2 | Flask-Login==0.6.2
3 | Flask-WTF==1.0.1
4 |
--------------------------------------------------------------------------------
/flask-spa-same-origin/frontend/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules/
2 | /public/build/
3 |
4 | .DS_Store
5 |
--------------------------------------------------------------------------------
/flask-spa-cross-origin/frontend/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules/
2 | /public/build/
3 |
4 | .DS_Store
5 |
--------------------------------------------------------------------------------
/flask-spa-same-origin/backend/requirements.txt:
--------------------------------------------------------------------------------
1 | Flask==2.2.2
2 | Flask-Login==0.6.2
3 | Flask-WTF==1.0.1
4 |
--------------------------------------------------------------------------------
/flask-spa-same-origin/nginx/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM nginx:latest
2 | COPY ./nginx.conf /etc/nginx/nginx.conf
3 |
--------------------------------------------------------------------------------
/flask-spa-cross-origin/backend/requirements.txt:
--------------------------------------------------------------------------------
1 | Flask==2.2.2
2 | Flask-Cors==3.0.10
3 | Flask-Login==0.6.2
4 | Flask-WTF==1.0.1
5 |
--------------------------------------------------------------------------------
/flask-spa-jinja/public/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/testdrivenio/flask-spa-auth/HEAD/flask-spa-jinja/public/favicon.png
--------------------------------------------------------------------------------
/flask-spa-cross-origin/frontend/public/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/testdrivenio/flask-spa-auth/HEAD/flask-spa-cross-origin/frontend/public/favicon.png
--------------------------------------------------------------------------------
/flask-spa-jinja/src/main.js:
--------------------------------------------------------------------------------
1 | import App from './App.svelte';
2 |
3 | const app = new App({
4 | target: document.body,
5 | });
6 |
7 | export default app;
8 |
--------------------------------------------------------------------------------
/flask-spa-same-origin/frontend/public/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/testdrivenio/flask-spa-auth/HEAD/flask-spa-same-origin/frontend/public/favicon.png
--------------------------------------------------------------------------------
/flask-spa-cross-origin/frontend/src/main.js:
--------------------------------------------------------------------------------
1 | import App from './App.svelte';
2 |
3 | const app = new App({
4 | target: document.body,
5 | });
6 |
7 | export default app;
8 |
--------------------------------------------------------------------------------
/flask-spa-same-origin/frontend/src/main.js:
--------------------------------------------------------------------------------
1 | import App from './App.svelte';
2 |
3 | const app = new App({
4 | target: document.body,
5 | });
6 |
7 | export default app;
8 |
--------------------------------------------------------------------------------
/flask-spa-same-origin/README.md:
--------------------------------------------------------------------------------
1 | # Flask + Svelte Auth
2 |
3 | Session-based auth, decoupled front and backend, same origin
4 |
5 | ## Getting Started
6 |
7 | ```sh
8 | $ docker-compose up -d --build
9 | ```
10 |
11 | Test at [http://localhost:81/](http://localhost:81/)
12 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Flask SPA Auth
2 |
3 | https://testdriven.io/blog/flask-spa-auth/
4 |
5 | 1. [Frontend Served From flask](/flask-spa-jinja)
6 | 1. [Frontend Served Separately (Same domain)](/flask-spa-same-origin)
7 | 1. [Frontend Served Separately (Cross-domain)](/flask-spa-cross-origin)
8 |
--------------------------------------------------------------------------------
/flask-spa-jinja/README.md:
--------------------------------------------------------------------------------
1 | # Flask + Svelte Auth
2 |
3 | Session-based auth, frontend served up via Jinja, cross origin
4 |
5 | ## Getting Started
6 |
7 | ```sh
8 | $ python3.10 -m venv env
9 | $ source env/bin/activate
10 | $ pip install -r requirements.txt
11 |
12 | $ npm install
13 | $ npm run build
14 |
15 | $ python app.py
16 | ```
17 |
18 | Test at [http://localhost:5000/](http://localhost:5000/)
19 |
--------------------------------------------------------------------------------
/flask-spa-same-origin/backend/Dockerfile:
--------------------------------------------------------------------------------
1 | # pull the official base image
2 | FROM python:3.10-slim-buster
3 |
4 | # set the working directory
5 | WORKDIR /usr/src/app
6 |
7 | # set environment variables
8 | ENV PYTHONDONTWRITEBYTECODE 1
9 | ENV PYTHONUNBUFFERED 1
10 |
11 | # install dependencies
12 | RUN pip install --upgrade pip
13 | COPY ./requirements.txt .
14 | RUN pip install -r requirements.txt
15 |
16 | # add app
17 | COPY . .
18 |
19 | # start app
20 | CMD ["python", "app.py"]
21 |
--------------------------------------------------------------------------------
/flask-spa-same-origin/frontend/Dockerfile:
--------------------------------------------------------------------------------
1 | # pull the official base image
2 | FROM node:lts-alpine
3 |
4 | # set working directory
5 | WORKDIR /usr/src/app
6 |
7 | # add `/usr/src/app/node_modules/.bin` to $PATH
8 | ENV PATH /usr/src/app/node_modules/.bin:$PATH
9 | ENV HOST=0.0.0.0
10 |
11 | # install and cache app dependencies
12 | COPY package.json .
13 | COPY package-lock.json .
14 | RUN npm ci
15 | RUN npm install svelte@3.0.0 -g --silent
16 |
17 | # start app
18 | CMD ["npm", "run", "dev"]
19 |
--------------------------------------------------------------------------------
/flask-spa-cross-origin/README.md:
--------------------------------------------------------------------------------
1 | # Flask + Svelte Auth
2 |
3 | Session-based auth, decoupled front and backend, cross origin
4 |
5 | ## Getting Started
6 |
7 | Run Flask:
8 |
9 | ```sh
10 | $ cd backend
11 | $ python3.9 -m venv env
12 | $ source env/bin/activate
13 | $ pip install -r requirements.txt
14 | $ python app.py
15 | ```
16 |
17 | Run Svelte:
18 |
19 | ```sh
20 | $ cd frontend
21 | $ npm install
22 | $ npm run dev
23 | ```
24 |
25 | Test at [http://localhost:8080/](http://localhost:8080/)
26 |
--------------------------------------------------------------------------------
/flask-spa-cross-origin/frontend/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Svelte app
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/flask-spa-same-origin/frontend/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Svelte app
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/flask-spa-same-origin/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: "3.8"
2 |
3 | services:
4 | backend:
5 | build: ./backend
6 | volumes:
7 | - ./backend:/usr/src/app
8 | expose:
9 | - 5000
10 |
11 | frontend:
12 | stdin_open: true
13 | build: ./frontend
14 | volumes:
15 | - ./frontend:/usr/src/app
16 | - /usr/src/app/node_modules
17 | expose:
18 | - 8080
19 | depends_on:
20 | - backend
21 |
22 | reverse_proxy:
23 | build: ./nginx
24 | ports:
25 | - 81:80
26 | depends_on:
27 | - backend
28 | - frontend
29 |
--------------------------------------------------------------------------------
/flask-spa-jinja/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "svelte-app",
3 | "version": "1.0.0",
4 | "private": true,
5 | "scripts": {
6 | "build": "rollup -c",
7 | "dev": "rollup -c -w",
8 | "start": "sirv public --no-clear"
9 | },
10 | "devDependencies": {
11 | "@rollup/plugin-commonjs": "^17.0.0",
12 | "@rollup/plugin-node-resolve": "^11.0.0",
13 | "rollup": "^2.3.4",
14 | "rollup-plugin-css-only": "^3.1.0",
15 | "rollup-plugin-livereload": "^2.0.0",
16 | "rollup-plugin-svelte": "^7.0.0",
17 | "rollup-plugin-terser": "^7.0.0",
18 | "svelte": "^3.0.0"
19 | },
20 | "dependencies": {
21 | "sirv-cli": "^2.0.0"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/flask-spa-cross-origin/frontend/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "svelte-app",
3 | "version": "1.0.0",
4 | "private": true,
5 | "scripts": {
6 | "build": "rollup -c",
7 | "dev": "rollup -c -w",
8 | "start": "sirv public --no-clear"
9 | },
10 | "devDependencies": {
11 | "@rollup/plugin-commonjs": "^17.0.0",
12 | "@rollup/plugin-node-resolve": "^11.0.0",
13 | "rollup": "^2.3.4",
14 | "rollup-plugin-css-only": "^3.1.0",
15 | "rollup-plugin-livereload": "^2.0.0",
16 | "rollup-plugin-svelte": "^7.0.0",
17 | "rollup-plugin-terser": "^7.0.0",
18 | "svelte": "^3.0.0"
19 | },
20 | "dependencies": {
21 | "sirv-cli": "^2.0.0"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/flask-spa-same-origin/frontend/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "svelte-app",
3 | "version": "1.0.0",
4 | "private": true,
5 | "scripts": {
6 | "build": "rollup -c",
7 | "dev": "rollup -c -w",
8 | "start": "sirv public --no-clear"
9 | },
10 | "devDependencies": {
11 | "@rollup/plugin-commonjs": "^17.0.0",
12 | "@rollup/plugin-node-resolve": "^11.0.0",
13 | "rollup": "^2.3.4",
14 | "rollup-plugin-css-only": "^3.1.0",
15 | "rollup-plugin-livereload": "^2.0.0",
16 | "rollup-plugin-svelte": "^7.0.0",
17 | "rollup-plugin-terser": "^7.0.0",
18 | "svelte": "^3.0.0"
19 | },
20 | "dependencies": {
21 | "sirv-cli": "^2.0.0"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/flask-spa-jinja/templates/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Svelte app
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 TestDriven.io
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/flask-spa-jinja/public/global.css:
--------------------------------------------------------------------------------
1 | html, body {
2 | position: relative;
3 | width: 100%;
4 | height: 100%;
5 | }
6 |
7 | body {
8 | color: #333;
9 | margin: 0;
10 | padding: 8px;
11 | box-sizing: border-box;
12 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
13 | }
14 |
15 | a {
16 | color: rgb(0,100,200);
17 | text-decoration: none;
18 | }
19 |
20 | a:hover {
21 | text-decoration: underline;
22 | }
23 |
24 | a:visited {
25 | color: rgb(0,80,160);
26 | }
27 |
28 | label {
29 | display: block;
30 | }
31 |
32 | input, button, select, textarea {
33 | font-family: inherit;
34 | font-size: inherit;
35 | -webkit-padding: 0.4em 0;
36 | padding: 0.4em;
37 | margin: 0 0 0.5em 0;
38 | box-sizing: border-box;
39 | border: 1px solid #ccc;
40 | border-radius: 2px;
41 | }
42 |
43 | input:disabled {
44 | color: #ccc;
45 | }
46 |
47 | button {
48 | color: #333;
49 | background-color: #f4f4f4;
50 | outline: none;
51 | }
52 |
53 | button:disabled {
54 | color: #999;
55 | }
56 |
57 | button:not(:disabled):active {
58 | background-color: #ddd;
59 | }
60 |
61 | button:focus {
62 | border-color: #666;
63 | }
64 |
--------------------------------------------------------------------------------
/flask-spa-cross-origin/frontend/public/global.css:
--------------------------------------------------------------------------------
1 | html, body {
2 | position: relative;
3 | width: 100%;
4 | height: 100%;
5 | }
6 |
7 | body {
8 | color: #333;
9 | margin: 0;
10 | padding: 8px;
11 | box-sizing: border-box;
12 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
13 | }
14 |
15 | a {
16 | color: rgb(0,100,200);
17 | text-decoration: none;
18 | }
19 |
20 | a:hover {
21 | text-decoration: underline;
22 | }
23 |
24 | a:visited {
25 | color: rgb(0,80,160);
26 | }
27 |
28 | label {
29 | display: block;
30 | }
31 |
32 | input, button, select, textarea {
33 | font-family: inherit;
34 | font-size: inherit;
35 | -webkit-padding: 0.4em 0;
36 | padding: 0.4em;
37 | margin: 0 0 0.5em 0;
38 | box-sizing: border-box;
39 | border: 1px solid #ccc;
40 | border-radius: 2px;
41 | }
42 |
43 | input:disabled {
44 | color: #ccc;
45 | }
46 |
47 | button {
48 | color: #333;
49 | background-color: #f4f4f4;
50 | outline: none;
51 | }
52 |
53 | button:disabled {
54 | color: #999;
55 | }
56 |
57 | button:not(:disabled):active {
58 | background-color: #ddd;
59 | }
60 |
61 | button:focus {
62 | border-color: #666;
63 | }
64 |
--------------------------------------------------------------------------------
/flask-spa-same-origin/frontend/public/global.css:
--------------------------------------------------------------------------------
1 | html, body {
2 | position: relative;
3 | width: 100%;
4 | height: 100%;
5 | }
6 |
7 | body {
8 | color: #333;
9 | margin: 0;
10 | padding: 8px;
11 | box-sizing: border-box;
12 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
13 | }
14 |
15 | a {
16 | color: rgb(0,100,200);
17 | text-decoration: none;
18 | }
19 |
20 | a:hover {
21 | text-decoration: underline;
22 | }
23 |
24 | a:visited {
25 | color: rgb(0,80,160);
26 | }
27 |
28 | label {
29 | display: block;
30 | }
31 |
32 | input, button, select, textarea {
33 | font-family: inherit;
34 | font-size: inherit;
35 | -webkit-padding: 0.4em 0;
36 | padding: 0.4em;
37 | margin: 0 0 0.5em 0;
38 | box-sizing: border-box;
39 | border: 1px solid #ccc;
40 | border-radius: 2px;
41 | }
42 |
43 | input:disabled {
44 | color: #ccc;
45 | }
46 |
47 | button {
48 | color: #333;
49 | background-color: #f4f4f4;
50 | outline: none;
51 | }
52 |
53 | button:disabled {
54 | color: #999;
55 | }
56 |
57 | button:not(:disabled):active {
58 | background-color: #ddd;
59 | }
60 |
61 | button:focus {
62 | border-color: #666;
63 | }
64 |
--------------------------------------------------------------------------------
/flask-spa-same-origin/nginx/nginx.conf:
--------------------------------------------------------------------------------
1 | user www-data;
2 | worker_processes auto;
3 | pid /run/nginx.pid;
4 | include /etc/nginx/modules-enabled/*.conf;
5 |
6 | events {
7 | worker_connections 1024;
8 | }
9 |
10 | http {
11 | server {
12 | listen 80;
13 |
14 | server_name localhost 127.0.0.1;
15 |
16 | location /api {
17 | proxy_pass http://backend:5000;
18 | proxy_http_version 1.1;
19 | proxy_redirect default;
20 | proxy_set_header Upgrade $http_upgrade;
21 | proxy_set_header Connection "upgrade";
22 | proxy_set_header Host $host;
23 | proxy_set_header X-Real-IP $remote_addr;
24 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
25 | proxy_set_header X-Forwarded-Host $server_name;
26 | }
27 |
28 | location / {
29 | proxy_pass http://frontend:8080;
30 | proxy_http_version 1.1;
31 | proxy_redirect default;
32 | proxy_set_header Upgrade $http_upgrade;
33 | proxy_set_header Connection "upgrade";
34 | proxy_set_header Host $host;
35 | proxy_set_header X-Real-IP $remote_addr;
36 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
37 | proxy_set_header X-Forwarded-Host $server_name;
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/flask-spa-jinja/rollup.config.js:
--------------------------------------------------------------------------------
1 | import svelte from 'rollup-plugin-svelte';
2 | import commonjs from '@rollup/plugin-commonjs';
3 | import resolve from '@rollup/plugin-node-resolve';
4 | import livereload from 'rollup-plugin-livereload';
5 | import { terser } from 'rollup-plugin-terser';
6 | import css from 'rollup-plugin-css-only';
7 |
8 | const production = !process.env.ROLLUP_WATCH;
9 |
10 | function serve() {
11 | let server;
12 |
13 | function toExit() {
14 | if (server) server.kill(0);
15 | }
16 |
17 | return {
18 | writeBundle() {
19 | if (server) return;
20 | server = require('child_process').spawn('npm', ['run', 'start', '--', '--dev'], {
21 | stdio: ['ignore', 'inherit', 'inherit'],
22 | shell: true
23 | });
24 |
25 | process.on('SIGTERM', toExit);
26 | process.on('exit', toExit);
27 | }
28 | };
29 | }
30 |
31 | export default {
32 | input: 'src/main.js',
33 | output: {
34 | sourcemap: true,
35 | format: 'iife',
36 | name: 'app',
37 | file: 'public/build/bundle.js'
38 | },
39 | plugins: [
40 | svelte({
41 | compilerOptions: {
42 | // enable run-time checks when not in production
43 | dev: !production
44 | }
45 | }),
46 | // we'll extract any component CSS out into
47 | // a separate file - better for performance
48 | css({ output: 'bundle.css' }),
49 |
50 | // If you have external dependencies installed from
51 | // npm, you'll most likely need these plugins. In
52 | // some cases you'll need additional configuration -
53 | // consult the documentation for details:
54 | // https://github.com/rollup/plugins/tree/master/packages/commonjs
55 | resolve({
56 | browser: true,
57 | dedupe: ['svelte']
58 | }),
59 | commonjs(),
60 |
61 | // In dev mode, call `npm run start` once
62 | // the bundle has been generated
63 | !production && serve(),
64 |
65 | // Watch the `public` directory and refresh the
66 | // browser on changes when not in production
67 | !production && livereload('public'),
68 |
69 | // If we're building for production (npm run build
70 | // instead of npm run dev), minify
71 | production && terser()
72 | ],
73 | watch: {
74 | clearScreen: false
75 | }
76 | };
77 |
--------------------------------------------------------------------------------
/flask-spa-cross-origin/frontend/rollup.config.js:
--------------------------------------------------------------------------------
1 | import svelte from 'rollup-plugin-svelte';
2 | import commonjs from '@rollup/plugin-commonjs';
3 | import resolve from '@rollup/plugin-node-resolve';
4 | import livereload from 'rollup-plugin-livereload';
5 | import { terser } from 'rollup-plugin-terser';
6 | import css from 'rollup-plugin-css-only';
7 |
8 | const production = !process.env.ROLLUP_WATCH;
9 |
10 | function serve() {
11 | let server;
12 |
13 | function toExit() {
14 | if (server) server.kill(0);
15 | }
16 |
17 | return {
18 | writeBundle() {
19 | if (server) return;
20 | server = require('child_process').spawn('npm', ['run', 'start', '--', '--dev'], {
21 | stdio: ['ignore', 'inherit', 'inherit'],
22 | shell: true
23 | });
24 |
25 | process.on('SIGTERM', toExit);
26 | process.on('exit', toExit);
27 | }
28 | };
29 | }
30 |
31 | export default {
32 | input: 'src/main.js',
33 | output: {
34 | sourcemap: true,
35 | format: 'iife',
36 | name: 'app',
37 | file: 'public/build/bundle.js'
38 | },
39 | plugins: [
40 | svelte({
41 | compilerOptions: {
42 | // enable run-time checks when not in production
43 | dev: !production
44 | }
45 | }),
46 | // we'll extract any component CSS out into
47 | // a separate file - better for performance
48 | css({ output: 'bundle.css' }),
49 |
50 | // If you have external dependencies installed from
51 | // npm, you'll most likely need these plugins. In
52 | // some cases you'll need additional configuration -
53 | // consult the documentation for details:
54 | // https://github.com/rollup/plugins/tree/master/packages/commonjs
55 | resolve({
56 | browser: true,
57 | dedupe: ['svelte']
58 | }),
59 | commonjs(),
60 |
61 | // In dev mode, call `npm run start` once
62 | // the bundle has been generated
63 | !production && serve(),
64 |
65 | // Watch the `public` directory and refresh the
66 | // browser on changes when not in production
67 | !production && livereload('public'),
68 |
69 | // If we're building for production (npm run build
70 | // instead of npm run dev), minify
71 | production && terser()
72 | ],
73 | watch: {
74 | clearScreen: false
75 | }
76 | };
77 |
--------------------------------------------------------------------------------
/flask-spa-same-origin/frontend/rollup.config.js:
--------------------------------------------------------------------------------
1 | import svelte from 'rollup-plugin-svelte';
2 | import commonjs from '@rollup/plugin-commonjs';
3 | import resolve from '@rollup/plugin-node-resolve';
4 | import livereload from 'rollup-plugin-livereload';
5 | import { terser } from 'rollup-plugin-terser';
6 | import css from 'rollup-plugin-css-only';
7 |
8 | const production = !process.env.ROLLUP_WATCH;
9 |
10 | function serve() {
11 | let server;
12 |
13 | function toExit() {
14 | if (server) server.kill(0);
15 | }
16 |
17 | return {
18 | writeBundle() {
19 | if (server) return;
20 | server = require('child_process').spawn('npm', ['run', 'start', '--', '--dev'], {
21 | stdio: ['ignore', 'inherit', 'inherit'],
22 | shell: true
23 | });
24 |
25 | process.on('SIGTERM', toExit);
26 | process.on('exit', toExit);
27 | }
28 | };
29 | }
30 |
31 | export default {
32 | input: 'src/main.js',
33 | output: {
34 | sourcemap: true,
35 | format: 'iife',
36 | name: 'app',
37 | file: 'public/build/bundle.js'
38 | },
39 | plugins: [
40 | svelte({
41 | compilerOptions: {
42 | // enable run-time checks when not in production
43 | dev: !production
44 | }
45 | }),
46 | // we'll extract any component CSS out into
47 | // a separate file - better for performance
48 | css({ output: 'bundle.css' }),
49 |
50 | // If you have external dependencies installed from
51 | // npm, you'll most likely need these plugins. In
52 | // some cases you'll need additional configuration -
53 | // consult the documentation for details:
54 | // https://github.com/rollup/plugins/tree/master/packages/commonjs
55 | resolve({
56 | browser: true,
57 | dedupe: ['svelte']
58 | }),
59 | commonjs(),
60 |
61 | // In dev mode, call `npm run start` once
62 | // the bundle has been generated
63 | !production && serve(),
64 |
65 | // Watch the `public` directory and refresh the
66 | // browser on changes when not in production
67 | !production && livereload('public'),
68 |
69 | // If we're building for production (npm run build
70 | // instead of npm run dev), minify
71 | production && terser()
72 | ],
73 | watch: {
74 | clearScreen: false
75 | }
76 | };
77 |
--------------------------------------------------------------------------------
/flask-spa-jinja/app.py:
--------------------------------------------------------------------------------
1 | from flask import Flask, jsonify, render_template, request
2 | from flask_login import (
3 | LoginManager,
4 | UserMixin,
5 | current_user,
6 | login_required,
7 | login_user,
8 | logout_user,
9 | )
10 | from flask_wtf.csrf import CSRFProtect
11 |
12 | app = Flask(__name__, static_folder="public")
13 | app.config.update(
14 | DEBUG=True,
15 | SECRET_KEY="secret_sauce",
16 | SESSION_COOKIE_HTTPONLY=True,
17 | REMEMBER_COOKIE_HTTPONLY=True,
18 | SESSION_COOKIE_SAMESITE="Strict",
19 | )
20 |
21 | login_manager = LoginManager()
22 | login_manager.init_app(app)
23 | login_manager.session_protection = "strong"
24 |
25 | csrf = CSRFProtect(app)
26 |
27 | # database
28 | users = [
29 | {
30 | "id": 1,
31 | "username": "test",
32 | "password": "test",
33 | }
34 | ]
35 |
36 |
37 | class User(UserMixin):
38 | ...
39 |
40 |
41 | def get_user(user_id: int):
42 | for user in users:
43 | if int(user["id"]) == int(user_id):
44 | return user
45 | return None
46 |
47 |
48 | @login_manager.user_loader
49 | def user_loader(id: int):
50 | user = get_user(id)
51 | if user:
52 | user_model = User()
53 | user_model.id = user["id"]
54 | return user_model
55 | return None
56 |
57 |
58 | @app.route("/", defaults={"path": ""})
59 | @app.route("/")
60 | def home(path):
61 | return render_template("index.html")
62 |
63 |
64 | @app.route("/api/login", methods=["POST"])
65 | def login():
66 | data = request.json
67 | username = data.get("username")
68 | password = data.get("password")
69 |
70 | for user in users:
71 | if user["username"] == username and user["password"] == password:
72 | user_model = User()
73 | user_model.id = user["id"]
74 | login_user(user_model)
75 | return jsonify({"login": True})
76 |
77 | return jsonify({"login": False})
78 |
79 |
80 | @app.route("/api/data", methods=["GET"])
81 | @login_required
82 | def user_data():
83 | user = get_user(current_user.id)
84 | return jsonify({"username": user["username"]})
85 |
86 |
87 | @app.route("/api/getsession")
88 | def check_session():
89 | if current_user.is_authenticated:
90 | return jsonify({"login": True})
91 |
92 | return jsonify({"login": False})
93 |
94 |
95 | @app.route("/api/logout")
96 | @login_required
97 | def logout():
98 | logout_user()
99 | return jsonify({"logout": True})
100 |
101 |
102 | if __name__ == "__main__":
103 | app.run(debug=True, load_dotenv=True)
104 |
--------------------------------------------------------------------------------
/flask-spa-jinja/src/App.svelte:
--------------------------------------------------------------------------------
1 |
80 |
81 |
86 |
87 |
88 |
89 | {#if isAuthenticated}
90 |
You are authenticated!
91 |
92 |
93 | {:else}
94 | Log in
95 |
103 | {/if}
104 |
105 |
106 |
--------------------------------------------------------------------------------
/flask-spa-same-origin/backend/app.py:
--------------------------------------------------------------------------------
1 | from flask import Flask, jsonify, request
2 | from flask_login import (
3 | LoginManager,
4 | UserMixin,
5 | current_user,
6 | login_required,
7 | login_user,
8 | logout_user,
9 | )
10 | from flask_wtf.csrf import CSRFProtect, generate_csrf
11 |
12 | app = Flask(__name__, static_folder="public")
13 | app.config.update(
14 | DEBUG=True,
15 | SECRET_KEY="secret_sauce",
16 | SESSION_COOKIE_HTTPONLY=True,
17 | REMEMBER_COOKIE_HTTPONLY=True,
18 | SESSION_COOKIE_SAMESITE="Strict",
19 | )
20 |
21 | login_manager = LoginManager()
22 | login_manager.init_app(app)
23 | login_manager.session_protection = "strong"
24 |
25 | csrf = CSRFProtect(app)
26 |
27 | # database
28 | users = [
29 | {
30 | "id": 1,
31 | "username": "test",
32 | "password": "test",
33 | }
34 | ]
35 |
36 |
37 | class User(UserMixin):
38 | ...
39 |
40 |
41 | def get_user(user_id: int):
42 | for user in users:
43 | if int(user["id"]) == int(user_id):
44 | return user
45 | return None
46 |
47 |
48 | @login_manager.user_loader
49 | def user_loader(id: int):
50 | user = get_user(id)
51 | if user:
52 | user_model = User()
53 | user_model.id = user["id"]
54 | return user_model
55 | return None
56 |
57 |
58 | @app.route("/api/ping", methods=["GET"])
59 | def home():
60 | return jsonify({"ping": "pong!"})
61 |
62 |
63 | @app.route("/api/getcsrf", methods=["GET"])
64 | def get_csrf():
65 | token = generate_csrf()
66 | response = jsonify({"detail": "CSRF cookie set"})
67 | response.headers.set("X-CSRFToken", token)
68 | return response
69 |
70 |
71 | @app.route("/api/login", methods=["POST"])
72 | def login():
73 | data = request.json
74 | username = data.get("username")
75 | password = data.get("password")
76 |
77 | for user in users:
78 | if user["username"] == username and user["password"] == password:
79 | user_model = User()
80 | user_model.id = user["id"]
81 | login_user(user_model)
82 | return jsonify({"login": True})
83 |
84 | return jsonify({"login": False})
85 |
86 |
87 | @app.route("/api/data", methods=["GET"])
88 | @login_required
89 | def user_data():
90 | user = get_user(current_user.id)
91 | return jsonify({"username": user["username"]})
92 |
93 |
94 | @app.route("/api/getsession")
95 | def check_session():
96 | if current_user.is_authenticated:
97 | return jsonify({"login": True})
98 |
99 | return jsonify({"login": False})
100 |
101 |
102 | @app.route("/api/logout")
103 | @login_required
104 | def logout():
105 | logout_user()
106 | return jsonify({"logout": True})
107 |
108 |
109 | if __name__ == "__main__":
110 | app.run(debug=True, host="0.0.0.0")
111 |
--------------------------------------------------------------------------------
/flask-spa-cross-origin/backend/app.py:
--------------------------------------------------------------------------------
1 | from flask import Flask, jsonify, request
2 | from flask_cors import CORS
3 | from flask_login import (
4 | LoginManager,
5 | UserMixin,
6 | current_user,
7 | login_required,
8 | login_user,
9 | logout_user,
10 | )
11 | from flask_wtf.csrf import CSRFProtect, generate_csrf
12 |
13 | app = Flask(__name__, static_folder="public")
14 | app.config.update(
15 | DEBUG=True,
16 | SECRET_KEY="secret_sauce",
17 | SESSION_COOKIE_HTTPONLY=True,
18 | REMEMBER_COOKIE_HTTPONLY=True,
19 | SESSION_COOKIE_SAMESITE="Lax",
20 | )
21 |
22 | login_manager = LoginManager()
23 | login_manager.init_app(app)
24 | login_manager.session_protection = "strong"
25 |
26 | csrf = CSRFProtect(app)
27 | cors = CORS(
28 | app,
29 | resources={r"*": {"origins": "http://localhost:8080"}},
30 | expose_headers=["Content-Type", "X-CSRFToken"],
31 | supports_credentials=True,
32 | )
33 |
34 | # database
35 | users = [
36 | {
37 | "id": 1,
38 | "username": "test",
39 | "password": "test",
40 | }
41 | ]
42 |
43 |
44 | class User(UserMixin):
45 | ...
46 |
47 |
48 | def get_user(user_id: int):
49 | for user in users:
50 | if int(user["id"]) == int(user_id):
51 | return user
52 | return None
53 |
54 |
55 | @login_manager.user_loader
56 | def user_loader(id: int):
57 | user = get_user(id)
58 | if user:
59 | user_model = User()
60 | user_model.id = user["id"]
61 | return user_model
62 | return None
63 |
64 |
65 | @app.route("/api/ping", methods=["GET"])
66 | def home():
67 | return jsonify({"ping": "pong!"})
68 |
69 |
70 | @app.route("/api/getcsrf", methods=["GET"])
71 | def get_csrf():
72 | token = generate_csrf()
73 | response = jsonify({"detail": "CSRF cookie set"})
74 | response.headers.set("X-CSRFToken", token)
75 | return response
76 |
77 |
78 | @app.route("/api/login", methods=["POST"])
79 | def login():
80 | data = request.json
81 | username = data.get("username")
82 | password = data.get("password")
83 |
84 | for user in users:
85 | if user["username"] == username and user["password"] == password:
86 | user_model = User()
87 | user_model.id = user["id"]
88 | login_user(user_model)
89 | return jsonify({"login": True})
90 |
91 | return jsonify({"login": False})
92 |
93 |
94 | @app.route("/api/data", methods=["GET"])
95 | @login_required
96 | def user_data():
97 | user = get_user(current_user.id)
98 | return jsonify({"username": user["username"]})
99 |
100 |
101 | @app.route("/api/getsession", methods=["GET"])
102 | def check_session():
103 | if current_user.is_authenticated:
104 | return jsonify({"login": True})
105 |
106 | return jsonify({"login": False})
107 |
108 |
109 | @app.route("/api/logout", methods=["GET"])
110 | @login_required
111 | def logout():
112 | logout_user()
113 | return jsonify({"logout": True})
114 |
115 |
116 | if __name__ == "__main__":
117 | app.run(debug=True)
118 |
--------------------------------------------------------------------------------
/flask-spa-same-origin/frontend/src/App.svelte:
--------------------------------------------------------------------------------
1 |
94 |
95 |
100 |
101 |
102 |
103 | {#if isAuthenticated}
104 |
You are authenticated!
105 |
106 |
107 | {:else}
108 | Log in
109 |
117 | {/if}
118 |
119 |
120 |
--------------------------------------------------------------------------------
/flask-spa-cross-origin/frontend/src/App.svelte:
--------------------------------------------------------------------------------
1 |
95 |
96 |
101 |
102 |
103 |
104 | {#if isAuthenticated}
105 |
You are authenticated!
106 |
107 |
108 | {:else}
109 | Log in
110 |
118 | {/if}
119 |
120 |
121 |
--------------------------------------------------------------------------------
/flask-spa-jinja/scripts/setupTypeScript.js:
--------------------------------------------------------------------------------
1 | // @ts-check
2 |
3 | /** This script modifies the project to support TS code in .svelte files like:
4 |
5 |
8 |
9 | As well as validating the code for CI.
10 | */
11 |
12 | /** To work on this script:
13 | rm -rf test-template template && git clone sveltejs/template test-template && node scripts/setupTypeScript.js test-template
14 | */
15 |
16 | const fs = require("fs")
17 | const path = require("path")
18 | const { argv } = require("process")
19 |
20 | const projectRoot = argv[2] || path.join(__dirname, "..")
21 |
22 | // Add deps to pkg.json
23 | const packageJSON = JSON.parse(fs.readFileSync(path.join(projectRoot, "package.json"), "utf8"))
24 | packageJSON.devDependencies = Object.assign(packageJSON.devDependencies, {
25 | "svelte-check": "^2.0.0",
26 | "svelte-preprocess": "^4.0.0",
27 | "@rollup/plugin-typescript": "^8.0.0",
28 | "typescript": "^4.0.0",
29 | "tslib": "^2.0.0",
30 | "@tsconfig/svelte": "^2.0.0"
31 | })
32 |
33 | // Add script for checking
34 | packageJSON.scripts = Object.assign(packageJSON.scripts, {
35 | "check": "svelte-check --tsconfig ./tsconfig.json"
36 | })
37 |
38 | // Write the package JSON
39 | fs.writeFileSync(path.join(projectRoot, "package.json"), JSON.stringify(packageJSON, null, " "))
40 |
41 | // mv src/main.js to main.ts - note, we need to edit rollup.config.js for this too
42 | const beforeMainJSPath = path.join(projectRoot, "src", "main.js")
43 | const afterMainTSPath = path.join(projectRoot, "src", "main.ts")
44 | fs.renameSync(beforeMainJSPath, afterMainTSPath)
45 |
46 | // Switch the app.svelte file to use TS
47 | const appSveltePath = path.join(projectRoot, "src", "App.svelte")
48 | let appFile = fs.readFileSync(appSveltePath, "utf8")
49 | appFile = appFile.replace("
8 |
9 | As well as validating the code for CI.
10 | */
11 |
12 | /** To work on this script:
13 | rm -rf test-template template && git clone sveltejs/template test-template && node scripts/setupTypeScript.js test-template
14 | */
15 |
16 | const fs = require("fs")
17 | const path = require("path")
18 | const { argv } = require("process")
19 |
20 | const projectRoot = argv[2] || path.join(__dirname, "..")
21 |
22 | // Add deps to pkg.json
23 | const packageJSON = JSON.parse(fs.readFileSync(path.join(projectRoot, "package.json"), "utf8"))
24 | packageJSON.devDependencies = Object.assign(packageJSON.devDependencies, {
25 | "svelte-check": "^2.0.0",
26 | "svelte-preprocess": "^4.0.0",
27 | "@rollup/plugin-typescript": "^8.0.0",
28 | "typescript": "^4.0.0",
29 | "tslib": "^2.0.0",
30 | "@tsconfig/svelte": "^2.0.0"
31 | })
32 |
33 | // Add script for checking
34 | packageJSON.scripts = Object.assign(packageJSON.scripts, {
35 | "check": "svelte-check --tsconfig ./tsconfig.json"
36 | })
37 |
38 | // Write the package JSON
39 | fs.writeFileSync(path.join(projectRoot, "package.json"), JSON.stringify(packageJSON, null, " "))
40 |
41 | // mv src/main.js to main.ts - note, we need to edit rollup.config.js for this too
42 | const beforeMainJSPath = path.join(projectRoot, "src", "main.js")
43 | const afterMainTSPath = path.join(projectRoot, "src", "main.ts")
44 | fs.renameSync(beforeMainJSPath, afterMainTSPath)
45 |
46 | // Switch the app.svelte file to use TS
47 | const appSveltePath = path.join(projectRoot, "src", "App.svelte")
48 | let appFile = fs.readFileSync(appSveltePath, "utf8")
49 | appFile = appFile.replace("
8 |
9 | As well as validating the code for CI.
10 | */
11 |
12 | /** To work on this script:
13 | rm -rf test-template template && git clone sveltejs/template test-template && node scripts/setupTypeScript.js test-template
14 | */
15 |
16 | const fs = require("fs")
17 | const path = require("path")
18 | const { argv } = require("process")
19 |
20 | const projectRoot = argv[2] || path.join(__dirname, "..")
21 |
22 | // Add deps to pkg.json
23 | const packageJSON = JSON.parse(fs.readFileSync(path.join(projectRoot, "package.json"), "utf8"))
24 | packageJSON.devDependencies = Object.assign(packageJSON.devDependencies, {
25 | "svelte-check": "^2.0.0",
26 | "svelte-preprocess": "^4.0.0",
27 | "@rollup/plugin-typescript": "^8.0.0",
28 | "typescript": "^4.0.0",
29 | "tslib": "^2.0.0",
30 | "@tsconfig/svelte": "^2.0.0"
31 | })
32 |
33 | // Add script for checking
34 | packageJSON.scripts = Object.assign(packageJSON.scripts, {
35 | "check": "svelte-check --tsconfig ./tsconfig.json"
36 | })
37 |
38 | // Write the package JSON
39 | fs.writeFileSync(path.join(projectRoot, "package.json"), JSON.stringify(packageJSON, null, " "))
40 |
41 | // mv src/main.js to main.ts - note, we need to edit rollup.config.js for this too
42 | const beforeMainJSPath = path.join(projectRoot, "src", "main.js")
43 | const afterMainTSPath = path.join(projectRoot, "src", "main.ts")
44 | fs.renameSync(beforeMainJSPath, afterMainTSPath)
45 |
46 | // Switch the app.svelte file to use TS
47 | const appSveltePath = path.join(projectRoot, "src", "App.svelte")
48 | let appFile = fs.readFileSync(appSveltePath, "utf8")
49 | appFile = appFile.replace("