Add file store
This commit is contained in:
parent
da2b13597a
commit
371379e981
64 changed files with 327 additions and 8343 deletions
|
@ -1 +0,0 @@
|
||||||
*
|
|
|
@ -1,49 +0,0 @@
|
||||||
{
|
|
||||||
"$id": "https://schemas.ory.sh/presets/kratos/quickstart/email-password/identity.schema.json",
|
|
||||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
||||||
"title": "Person",
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"traits": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"email": {
|
|
||||||
"type": "string",
|
|
||||||
"format": "email",
|
|
||||||
"title": "E-Mail",
|
|
||||||
"minLength": 3,
|
|
||||||
"ory.sh/kratos": {
|
|
||||||
"credentials": {
|
|
||||||
"password": {
|
|
||||||
"identifier": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"verification": {
|
|
||||||
"via": "email"
|
|
||||||
},
|
|
||||||
"recovery": {
|
|
||||||
"via": "email"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"name": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"first": {
|
|
||||||
"title": "First Name",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"last": {
|
|
||||||
"title": "Last Name",
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": [
|
|
||||||
"email"
|
|
||||||
],
|
|
||||||
"additionalProperties": false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,91 +0,0 @@
|
||||||
version: v0.8.0-alpha.3
|
|
||||||
|
|
||||||
dsn: memory
|
|
||||||
|
|
||||||
serve:
|
|
||||||
public:
|
|
||||||
base_url: http://localhost:4433/
|
|
||||||
cors:
|
|
||||||
enabled: true
|
|
||||||
allowed_origins:
|
|
||||||
- http://localhost:3000
|
|
||||||
allowed_methods:
|
|
||||||
- POST
|
|
||||||
- GET
|
|
||||||
- PUT
|
|
||||||
- PATCH
|
|
||||||
- DELETE
|
|
||||||
allowed_headers:
|
|
||||||
- Authorization
|
|
||||||
- Cookie
|
|
||||||
- Content-Type
|
|
||||||
exposed_headers:
|
|
||||||
- Content-Type
|
|
||||||
- Set-Cookie
|
|
||||||
admin:
|
|
||||||
base_url: http://kratos:4434/
|
|
||||||
|
|
||||||
selfservice:
|
|
||||||
default_browser_return_url: http://localhost:3000/
|
|
||||||
allowed_return_urls:
|
|
||||||
- http://localhost:3000
|
|
||||||
|
|
||||||
methods:
|
|
||||||
password:
|
|
||||||
enabled: true
|
|
||||||
|
|
||||||
flows:
|
|
||||||
error:
|
|
||||||
ui_url: http://localhost:3000/auth/login
|
|
||||||
|
|
||||||
settings:
|
|
||||||
ui_url: http://localhost:3000/auth/settings
|
|
||||||
privileged_session_max_age: 15m
|
|
||||||
|
|
||||||
recovery:
|
|
||||||
enabled: true
|
|
||||||
ui_url: http://localhost:3000/auth/recovery
|
|
||||||
|
|
||||||
verification:
|
|
||||||
enabled: true
|
|
||||||
ui_url: http://localhost:3000/auth/verification
|
|
||||||
after:
|
|
||||||
default_browser_return_url: http://localhost:3000/
|
|
||||||
|
|
||||||
logout:
|
|
||||||
after:
|
|
||||||
default_browser_return_url: http://localhost:3000/auth/login
|
|
||||||
|
|
||||||
login:
|
|
||||||
ui_url: http://localhost:3000/auth/login
|
|
||||||
|
|
||||||
registration:
|
|
||||||
ui_url: http://localhost:3000/auth/registration
|
|
||||||
after:
|
|
||||||
password:
|
|
||||||
hooks:
|
|
||||||
-
|
|
||||||
hook: session
|
|
||||||
|
|
||||||
log:
|
|
||||||
level: info
|
|
||||||
format: text
|
|
||||||
|
|
||||||
secrets:
|
|
||||||
cookie:
|
|
||||||
- PLEASE-CHANGE-ME-I-AM-VERY-INSECURE
|
|
||||||
|
|
||||||
hashers:
|
|
||||||
algorithm: bcrypt
|
|
||||||
bcrypt:
|
|
||||||
cost: 8
|
|
||||||
|
|
||||||
identity:
|
|
||||||
default_schema_id: preset://email
|
|
||||||
schemas:
|
|
||||||
- id: preset://email
|
|
||||||
url: file:///etc/config/kratos/identity.schema.json
|
|
||||||
|
|
||||||
courier:
|
|
||||||
smtp:
|
|
||||||
connection_uri: smtps://test:test@mailslurper:1025/?skip_ssl_verify=true
|
|
|
@ -1,42 +0,0 @@
|
||||||
version: '3.7'
|
|
||||||
|
|
||||||
services:
|
|
||||||
kratos:
|
|
||||||
volumes:
|
|
||||||
- type: volume
|
|
||||||
source: kratos-sqlite
|
|
||||||
target: /var/lib/sqlite
|
|
||||||
read_only: false
|
|
||||||
- type: bind
|
|
||||||
source: ./contrib/quickstart/kratos/cloud
|
|
||||||
target: /etc/config/kratos
|
|
||||||
kratos-migrate:
|
|
||||||
volumes:
|
|
||||||
- type: volume
|
|
||||||
source: kratos-sqlite
|
|
||||||
target: /var/lib/sqlite
|
|
||||||
read_only: false
|
|
||||||
- type: bind
|
|
||||||
source: ./contrib/quickstart/kratos/cloud
|
|
||||||
target: /etc/config/kratos
|
|
||||||
|
|
||||||
# kratos-selfservice-ui-node:
|
|
||||||
# ports:
|
|
||||||
# - "4438:4438"
|
|
||||||
# environment:
|
|
||||||
# - PORT=4438
|
|
||||||
# - KRATOS_BROWSER_URL=http://localhost:4455/
|
|
||||||
|
|
||||||
# kratos-caddy:
|
|
||||||
# image: caddy:2.4.5-alpine
|
|
||||||
# ports:
|
|
||||||
# - "4455:4455"
|
|
||||||
# volumes:
|
|
||||||
# - type: bind
|
|
||||||
# source: ./contrib/quickstart/kratos/cloud/Caddyfile
|
|
||||||
# target: /etc/caddy/Caddyfile
|
|
||||||
# command: caddy run -watch -config /etc/caddy/Caddyfile
|
|
||||||
# restart: on-failure
|
|
||||||
# networks:
|
|
||||||
# - intranet
|
|
||||||
|
|
|
@ -1,49 +0,0 @@
|
||||||
{
|
|
||||||
"$id": "https://schemas.ory.sh/presets/kratos/quickstart/email-password/identity.schema.json",
|
|
||||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
||||||
"title": "Person",
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"traits": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"email": {
|
|
||||||
"type": "string",
|
|
||||||
"format": "email",
|
|
||||||
"title": "E-Mail",
|
|
||||||
"minLength": 3,
|
|
||||||
"ory.sh/kratos": {
|
|
||||||
"credentials": {
|
|
||||||
"password": {
|
|
||||||
"identifier": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"verification": {
|
|
||||||
"via": "email"
|
|
||||||
},
|
|
||||||
"recovery": {
|
|
||||||
"via": "email"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"name": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"first": {
|
|
||||||
"title": "First Name",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"last": {
|
|
||||||
"title": "Last Name",
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": [
|
|
||||||
"email"
|
|
||||||
],
|
|
||||||
"additionalProperties": false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,84 +0,0 @@
|
||||||
version: v0.7.1-alpha.1
|
|
||||||
|
|
||||||
dsn: memory
|
|
||||||
|
|
||||||
serve:
|
|
||||||
public:
|
|
||||||
base_url: http://127.0.0.1:4433/
|
|
||||||
cors:
|
|
||||||
enabled: true
|
|
||||||
admin:
|
|
||||||
base_url: http://kratos:4434/
|
|
||||||
|
|
||||||
selfservice:
|
|
||||||
default_browser_return_url: http://127.0.0.1:4455/
|
|
||||||
allowed_return_urls:
|
|
||||||
- http://127.0.0.1:4455
|
|
||||||
|
|
||||||
methods:
|
|
||||||
password:
|
|
||||||
enabled: true
|
|
||||||
|
|
||||||
flows:
|
|
||||||
error:
|
|
||||||
ui_url: http://127.0.0.1:4455/error
|
|
||||||
|
|
||||||
settings:
|
|
||||||
ui_url: http://127.0.0.1:4455/settings
|
|
||||||
privileged_session_max_age: 15m
|
|
||||||
|
|
||||||
recovery:
|
|
||||||
enabled: true
|
|
||||||
ui_url: http://127.0.0.1:4455/recovery
|
|
||||||
|
|
||||||
verification:
|
|
||||||
enabled: true
|
|
||||||
ui_url: http://127.0.0.1:4455/verification
|
|
||||||
after:
|
|
||||||
default_browser_return_url: http://127.0.0.1:4455/
|
|
||||||
|
|
||||||
logout:
|
|
||||||
after:
|
|
||||||
default_browser_return_url: http://127.0.0.1:4455/login
|
|
||||||
|
|
||||||
login:
|
|
||||||
ui_url: http://127.0.0.1:4455/login
|
|
||||||
lifespan: 10m
|
|
||||||
|
|
||||||
registration:
|
|
||||||
lifespan: 10m
|
|
||||||
ui_url: http://127.0.0.1:4455/registration
|
|
||||||
after:
|
|
||||||
password:
|
|
||||||
hooks:
|
|
||||||
-
|
|
||||||
hook: session
|
|
||||||
|
|
||||||
log:
|
|
||||||
level: debug
|
|
||||||
format: text
|
|
||||||
leak_sensitive_values: true
|
|
||||||
|
|
||||||
secrets:
|
|
||||||
cookie:
|
|
||||||
- PLEASE-CHANGE-ME-I-AM-VERY-INSECURE
|
|
||||||
cipher:
|
|
||||||
- 32-LONG-SECRET-NOT-SECURE-AT-ALL
|
|
||||||
|
|
||||||
ciphers:
|
|
||||||
algorithm: xchacha20-poly1305
|
|
||||||
|
|
||||||
hashers:
|
|
||||||
algorithm: bcrypt
|
|
||||||
bcrypt:
|
|
||||||
cost: 8
|
|
||||||
|
|
||||||
identity:
|
|
||||||
default_schema_id: default
|
|
||||||
schemas:
|
|
||||||
- id: default
|
|
||||||
url: file:///etc/config/kratos/identity.schema.json
|
|
||||||
|
|
||||||
courier:
|
|
||||||
smtp:
|
|
||||||
connection_uri: smtps://test:test@mailslurper:1025/?skip_ssl_verify=true
|
|
|
@ -1,40 +0,0 @@
|
||||||
{
|
|
||||||
"$id": "https://schemas.ory.sh/presets/kratos/quickstart/email-password/identity.schema.json",
|
|
||||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
||||||
"title": "Person",
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"traits": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"email": {
|
|
||||||
"type": "string",
|
|
||||||
"format": "email",
|
|
||||||
"title": "E-Mail",
|
|
||||||
"minLength": 3,
|
|
||||||
"ory.sh/kratos": {
|
|
||||||
"credentials": {
|
|
||||||
"password": {
|
|
||||||
"identifier": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"verification": {
|
|
||||||
"via": "email"
|
|
||||||
},
|
|
||||||
"recovery": {
|
|
||||||
"via": "email"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"website": {
|
|
||||||
"type": "object"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": [
|
|
||||||
"website",
|
|
||||||
"email"
|
|
||||||
],
|
|
||||||
"additionalProperties": false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,17 +0,0 @@
|
||||||
local claims = {
|
|
||||||
email_verified: false
|
|
||||||
} + std.extVar('claims');
|
|
||||||
|
|
||||||
{
|
|
||||||
identity: {
|
|
||||||
traits: {
|
|
||||||
// Allowing unverified email addresses enables account
|
|
||||||
// enumeration attacks, especially if the value is used for
|
|
||||||
// e.g. verification or as a password login identifier.
|
|
||||||
//
|
|
||||||
// Therefore we only return the email if it (a) exists and (b) is marked verified
|
|
||||||
// by GitHub.
|
|
||||||
[if "email" in claims && claims.email_verified then "email" else null]: claims.email,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
|
@ -1,60 +0,0 @@
|
||||||
-
|
|
||||||
id: "ory:kratos:public"
|
|
||||||
upstream:
|
|
||||||
preserve_host: true
|
|
||||||
url: "http://kratos:4433"
|
|
||||||
strip_path: /.ory/kratos/public
|
|
||||||
match:
|
|
||||||
url: "http://127.0.0.1:4455/.ory/kratos/public/<**>"
|
|
||||||
methods:
|
|
||||||
- GET
|
|
||||||
- POST
|
|
||||||
- PUT
|
|
||||||
- DELETE
|
|
||||||
- PATCH
|
|
||||||
authenticators:
|
|
||||||
-
|
|
||||||
handler: noop
|
|
||||||
authorizer:
|
|
||||||
handler: allow
|
|
||||||
mutators:
|
|
||||||
- handler: noop
|
|
||||||
|
|
||||||
-
|
|
||||||
id: "ory:kratos-selfservice-ui-node:anonymous"
|
|
||||||
upstream:
|
|
||||||
preserve_host: true
|
|
||||||
url: "http://kratos-selfservice-ui-node:4435"
|
|
||||||
match:
|
|
||||||
url: "http://127.0.0.1:4455/<{registration,welcome,recovery,verification,login,error,**.css,**.js,**.png,}>"
|
|
||||||
methods:
|
|
||||||
- GET
|
|
||||||
authenticators:
|
|
||||||
-
|
|
||||||
handler: anonymous
|
|
||||||
authorizer:
|
|
||||||
handler: allow
|
|
||||||
mutators:
|
|
||||||
-
|
|
||||||
handler: noop
|
|
||||||
|
|
||||||
-
|
|
||||||
id: "ory:kratos-selfservice-ui-node:protected"
|
|
||||||
upstream:
|
|
||||||
preserve_host: true
|
|
||||||
url: "http://kratos-selfservice-ui-node:4435"
|
|
||||||
match:
|
|
||||||
url: "http://127.0.0.1:4455/<{debug,dashboard,settings}>"
|
|
||||||
methods:
|
|
||||||
- GET
|
|
||||||
authenticators:
|
|
||||||
-
|
|
||||||
handler: cookie_session
|
|
||||||
authorizer:
|
|
||||||
handler: allow
|
|
||||||
mutators:
|
|
||||||
- handler: id_token
|
|
||||||
errors:
|
|
||||||
- handler: redirect
|
|
||||||
config:
|
|
||||||
to: http://127.0.0.1:4455/login
|
|
|
@ -1,18 +0,0 @@
|
||||||
{
|
|
||||||
"keys": [
|
|
||||||
{
|
|
||||||
"use": "sig",
|
|
||||||
"kty": "RSA",
|
|
||||||
"kid": "a2aa9739-d753-4a0d-87ee-61f101050277",
|
|
||||||
"alg": "RS256",
|
|
||||||
"n": "zpjSl0ySsdk_YC4ZJYYV-cSznWkzndTo0lyvkYmeBkW60YHuHzXaviHqonY_DjFBdnZC0Vs_QTWmBlZvPzTp4Oni-eOetP-Ce3-B8jkGWpKFOjTLw7uwR3b3jm_mFNiz1dV_utWiweqx62Se0SyYaAXrgStU8-3P2Us7_kz5NnBVL1E7aEP40aB7nytLvPhXau-YhFmUfgykAcov0QrnNY0DH0eTcwL19UysvlKx6Uiu6mnbaFE1qx8X2m2xuLpErfiqj6wLCdCYMWdRTHiVsQMtTzSwuPuXfH7J06GTo3I1cEWN8Mb-RJxlosJA_q7hEd43yYisCO-8szX0lgCasw",
|
|
||||||
"e": "AQAB",
|
|
||||||
"d": "x3dfY_rna1UQTmFToBoMn6Edte47irhkra4VSNPwwaeTTvI-oN2TO51td7vo91_xD1nw-0c5FFGi4V2UfRcudBv9LD1rHt_O8EPUh7QtAUeT3_XXgjx1Xxpqu5goMZpkTyGZ-B6JzOY3L8lvWQ_Qeia1EXpvxC-oTOjJnKZeuwIPlcoNKMRU-mIYOnkRFfnUvrDm7N9UZEp3PfI3vhE9AquP1PEvz5KTUYkubsfmupqqR6FmMUm6ulGT7guhBw9A3vxIYbYGKvXLdBvn68mENrEYxXrwmu6ITMh_y208M5rC-hgEHIAIvMu1aVW6jNgyQTunsGST3UyrSbwjI0K9UQ",
|
|
||||||
"p": "77fDvnfHRFEgyi7mh0c6fAdtMEMJ05W8NwTG_D-cSwfWipfTwJJrroWoRwEgdAg5AWGq-MNUzrubTVXoJdC2T4g1o-VRZkKKYoMvav3CvOIMzCBxBs9I_GAKr5NCSk7maksMqiCTMhmkoZ5RPuMYMY_YzxKNAbjBd9qFLfaVAqs",
|
|
||||||
"q": "3KEmPA2XQkf7dvtpY1Xkp1IfMV_UBdmYk7J6dB5BYqzviQWdEFvWaSATJ_7qV1dw0JDZynOgipp8gvoL-RepfjtArhPz41wB3J2xmBYrBr1sJ-x5eqAvMkQk2bd5KTor44e79TRIkmkFYAIdUQ5JdVXPA13S8WUZfb_bAbwaCBk",
|
|
||||||
"dp": "5uyy32AJkNFKchqeLsE6INMSp0RdSftbtfCfM86fZFQno5lA_qjOnO_avJPkTILDT4ZjqoKYxxJJOEXCffNCPPltGvbE5GrDXsUbP8k2-LgWNeoml7XFjIGEqcCFQoohQ1IK4DTDN6cmRh76C0e_Pbdh15D6TydJEIlsdGuu_kM",
|
|
||||||
"dq": "aegFNYCEojFxeTzX6vIZL2RRSt8oJKK-Be__reu0EUzYMtr5-RdMhev6phFMph54LfXKRc9ZOg9MQ4cJ5klAeDKzKpyzTukkj6U20b2aa8LTvxpZec6YuTVSxxu2Ul71IGRQijTNvVIiXWLGddk409Ub6Q7JqkyQfvdwhpWnnUk",
|
|
||||||
"qi": "P68-EwgcRy9ce_PZ75c909cU7dzCiaGcTX1psJiXmQAFBcG0msWfsyHGbllOZG27pKde78ORGJDYDNk1FqTwsogZyCP87EiBmOoqXWnMvKYfJ1DOx7x42LMAGwMD3bgQj9jgRACxFJG4n3NI6uFlFruyl_CLQzwW_rQFHshLK7Q"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -1,88 +0,0 @@
|
||||||
log:
|
|
||||||
level: debug
|
|
||||||
format: json
|
|
||||||
|
|
||||||
serve:
|
|
||||||
proxy:
|
|
||||||
cors:
|
|
||||||
enabled: true
|
|
||||||
allowed_origins:
|
|
||||||
- "*"
|
|
||||||
allowed_methods:
|
|
||||||
- POST
|
|
||||||
- GET
|
|
||||||
- PUT
|
|
||||||
- PATCH
|
|
||||||
- DELETE
|
|
||||||
allowed_headers:
|
|
||||||
- Authorization
|
|
||||||
- Content-Type
|
|
||||||
exposed_headers:
|
|
||||||
- Content-Type
|
|
||||||
allow_credentials: true
|
|
||||||
debug: true
|
|
||||||
|
|
||||||
errors:
|
|
||||||
fallback:
|
|
||||||
- json
|
|
||||||
|
|
||||||
handlers:
|
|
||||||
redirect:
|
|
||||||
enabled: true
|
|
||||||
config:
|
|
||||||
to: http://127.0.0.1:4455/login
|
|
||||||
when:
|
|
||||||
-
|
|
||||||
error:
|
|
||||||
- unauthorized
|
|
||||||
- forbidden
|
|
||||||
request:
|
|
||||||
header:
|
|
||||||
accept:
|
|
||||||
- text/html
|
|
||||||
json:
|
|
||||||
enabled: true
|
|
||||||
config:
|
|
||||||
verbose: true
|
|
||||||
|
|
||||||
access_rules:
|
|
||||||
matching_strategy: glob
|
|
||||||
repositories:
|
|
||||||
- file:///etc/config/oathkeeper/access-rules.yml
|
|
||||||
|
|
||||||
authenticators:
|
|
||||||
anonymous:
|
|
||||||
enabled: true
|
|
||||||
config:
|
|
||||||
subject: guest
|
|
||||||
|
|
||||||
cookie_session:
|
|
||||||
enabled: true
|
|
||||||
config:
|
|
||||||
check_session_url: http://kratos:4433/sessions/whoami
|
|
||||||
preserve_path: true
|
|
||||||
extra_from: "@this"
|
|
||||||
subject_from: "identity.id"
|
|
||||||
only:
|
|
||||||
- ory_kratos_session
|
|
||||||
|
|
||||||
noop:
|
|
||||||
enabled: true
|
|
||||||
|
|
||||||
authorizers:
|
|
||||||
allow:
|
|
||||||
enabled: true
|
|
||||||
|
|
||||||
mutators:
|
|
||||||
noop:
|
|
||||||
enabled: true
|
|
||||||
|
|
||||||
id_token:
|
|
||||||
enabled: true
|
|
||||||
config:
|
|
||||||
issuer_url: http://127.0.0.1:4455/
|
|
||||||
jwks_url: file:///etc/config/oathkeeper/id_token.jwks.json
|
|
||||||
claims: |
|
|
||||||
{
|
|
||||||
"session": {{ .Extra | toJson }}
|
|
||||||
}
|
|
|
@ -1,61 +0,0 @@
|
||||||
version: '3.7'
|
|
||||||
services:
|
|
||||||
kratos-migrate:
|
|
||||||
image: oryd/kratos:v0.10.1
|
|
||||||
environment:
|
|
||||||
- DSN=sqlite:///var/lib/sqlite/db.sqlite?_fk=true&mode=rwc
|
|
||||||
volumes:
|
|
||||||
- type: volume
|
|
||||||
source: kratos-sqlite
|
|
||||||
target: /var/lib/sqlite
|
|
||||||
read_only: false
|
|
||||||
- type: bind
|
|
||||||
source: ./contrib/quickstart/kratos/email-password
|
|
||||||
target: /etc/config/kratos
|
|
||||||
command: -c /etc/config/kratos/kratos.yml migrate sql -e --yes
|
|
||||||
restart: on-failure
|
|
||||||
networks:
|
|
||||||
- intranet
|
|
||||||
# kratos-selfservice-ui-node:
|
|
||||||
# image: oryd/kratos-selfservice-ui-node:v0.10.1
|
|
||||||
# environment:
|
|
||||||
# - KRATOS_PUBLIC_URL=http://kratos:4433/
|
|
||||||
# - KRATOS_BROWSER_URL=http://127.0.0.1:4433/
|
|
||||||
# networks:
|
|
||||||
# - intranet
|
|
||||||
# restart: on-failure
|
|
||||||
kratos:
|
|
||||||
depends_on:
|
|
||||||
- kratos-migrate
|
|
||||||
image: oryd/kratos:v0.10.1
|
|
||||||
ports:
|
|
||||||
- '4433:4433' # public
|
|
||||||
- '4434:4434' # admin
|
|
||||||
restart: unless-stopped
|
|
||||||
environment:
|
|
||||||
- DSN=sqlite:///var/lib/sqlite/db.sqlite?_fk=true
|
|
||||||
- LOG_LEVEL=trace
|
|
||||||
command: serve -c /etc/config/kratos/kratos.yml --dev --watch-courier
|
|
||||||
volumes:
|
|
||||||
- type: volume
|
|
||||||
source: kratos-sqlite
|
|
||||||
target: /var/lib/sqlite
|
|
||||||
read_only: false
|
|
||||||
- type: bind
|
|
||||||
source: ./contrib/quickstart/kratos/email-password
|
|
||||||
target: /etc/config/kratos
|
|
||||||
networks:
|
|
||||||
- intranet
|
|
||||||
# mailslurper:
|
|
||||||
# image: oryd/mailslurper:latest-smtps
|
|
||||||
# ports:
|
|
||||||
# - '4436:4436'
|
|
||||||
# - '4437:4437'
|
|
||||||
# networks:
|
|
||||||
# - intranet
|
|
||||||
|
|
||||||
networks:
|
|
||||||
intranet:
|
|
||||||
|
|
||||||
volumes:
|
|
||||||
kratos-sqlite:
|
|
2
frontend/.gitignore
vendored
2
frontend/.gitignore
vendored
|
@ -1,2 +0,0 @@
|
||||||
node_modules
|
|
||||||
.svelte-kit
|
|
|
@ -1,70 +0,0 @@
|
||||||
# kratos-selfservice-svelte-node
|
|
||||||
|
|
||||||
Self-service [Svelte](https://svelte.dev/) node for
|
|
||||||
[Ory Kratos](https://github.com/ory/kratos). It has no style or decoration.
|
|
||||||
Apply your custom style according to your application.
|
|
||||||
|
|
||||||
## Install
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git clone https://github.com/emrahcom/kratos-selfservice-svelte-node.git
|
|
||||||
|
|
||||||
cd kratos-selfservice-svelte-node
|
|
||||||
npm install
|
|
||||||
```
|
|
||||||
|
|
||||||
## Config
|
|
||||||
|
|
||||||
Change `src/lib/config.ts` according to your environment.
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
export const KRATOS = "https://___KRATOS_FQDN___";
|
|
||||||
export const APP = "https://___APP_FQDN___";
|
|
||||||
```
|
|
||||||
|
|
||||||
## Run (dev)
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm run dev -- --host --port 3000
|
|
||||||
```
|
|
||||||
|
|
||||||
## Run (prod)
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm run build
|
|
||||||
node build/index.js
|
|
||||||
```
|
|
||||||
|
|
||||||
## Pages
|
|
||||||
|
|
||||||
- Landing page
|
|
||||||
|
|
||||||
`/`
|
|
||||||
|
|
||||||
- Secure dashboard
|
|
||||||
|
|
||||||
`/dashboard`
|
|
||||||
|
|
||||||
- Registration
|
|
||||||
|
|
||||||
`/registration`
|
|
||||||
|
|
||||||
- Login
|
|
||||||
|
|
||||||
`/login`
|
|
||||||
|
|
||||||
- Settings
|
|
||||||
|
|
||||||
`/settings`
|
|
||||||
|
|
||||||
- Recovery
|
|
||||||
|
|
||||||
`/recovery`
|
|
||||||
|
|
||||||
- Verification
|
|
||||||
|
|
||||||
`/verification`
|
|
||||||
|
|
||||||
- Logout
|
|
||||||
|
|
||||||
`/logout`
|
|
6593
frontend/package-lock.json
generated
6593
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -1,37 +0,0 @@
|
||||||
{
|
|
||||||
"name": "kratos-svelte-ui",
|
|
||||||
"version": "0.0.1",
|
|
||||||
"private": true,
|
|
||||||
"scripts": {
|
|
||||||
"dev": "vite dev",
|
|
||||||
"build": "vite build",
|
|
||||||
"preview": "vite preview",
|
|
||||||
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
|
||||||
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
|
||||||
"lint": "prettier --plugin-search-dir . --check . && eslint .",
|
|
||||||
"format": "prettier --plugin-search-dir . --write ."
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@sveltejs/adapter-auto": "next",
|
|
||||||
"@sveltejs/adapter-node": "^1.0.0-next.94",
|
|
||||||
"@sveltejs/kit": "next",
|
|
||||||
"@typescript-eslint/eslint-plugin": "^5.27.0",
|
|
||||||
"@typescript-eslint/parser": "^5.27.0",
|
|
||||||
"eslint": "^8.16.0",
|
|
||||||
"eslint-config-prettier": "^8.3.0",
|
|
||||||
"eslint-plugin-svelte3": "^4.0.0",
|
|
||||||
"prettier": "^2.6.2",
|
|
||||||
"prettier-plugin-svelte": "^2.7.0",
|
|
||||||
"svelte": "^3.44.0",
|
|
||||||
"svelte-check": "^2.7.1",
|
|
||||||
"svelte-preprocess": "^4.10.6",
|
|
||||||
"tslib": "^2.3.1",
|
|
||||||
"typescript": "^4.7.4",
|
|
||||||
"vite": "^3.1.0"
|
|
||||||
},
|
|
||||||
"type": "module",
|
|
||||||
"dependencies": {
|
|
||||||
"@codemirror/lang-javascript": "^6.1.0",
|
|
||||||
"codemirror": "^6.0.1"
|
|
||||||
}
|
|
||||||
}
|
|
9
frontend/src/app.d.ts
vendored
9
frontend/src/app.d.ts
vendored
|
@ -1,9 +0,0 @@
|
||||||
// See https://kit.svelte.dev/docs/types#app
|
|
||||||
// for information about these interfaces
|
|
||||||
// and what to do when importing types
|
|
||||||
declare namespace App {
|
|
||||||
// interface Locals {}
|
|
||||||
// interface PageData {}
|
|
||||||
// interface PageError {}
|
|
||||||
// interface Platform {}
|
|
||||||
}
|
|
|
@ -1,16 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
|
|
||||||
<link
|
|
||||||
rel="stylesheet"
|
|
||||||
href="https://cdn.jsdelivr.net/npm/water.css@2/out/water.css"
|
|
||||||
/>
|
|
||||||
<meta name="viewport" content="width=device-width" />
|
|
||||||
%sveltekit.head%
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div>%sveltekit.body%</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
|
@ -1,139 +0,0 @@
|
||||||
<script context="module">
|
|
||||||
import { EditorView, minimalSetup, basicSetup } from "codemirror";
|
|
||||||
import { StateEffect } from "@codemirror/state";
|
|
||||||
export { minimalSetup, basicSetup };
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { onMount, onDestroy, createEventDispatcher } from "svelte";
|
|
||||||
const dispatch = createEventDispatcher();
|
|
||||||
|
|
||||||
let dom: any;
|
|
||||||
|
|
||||||
let _mounted = false;
|
|
||||||
|
|
||||||
onMount(() => {
|
|
||||||
_mounted = true;
|
|
||||||
return () => {
|
|
||||||
_mounted = false;
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
export let view: any = null;
|
|
||||||
|
|
||||||
/* `doc` is deliberately made non-reactive for not storing a reduntant string
|
|
||||||
besides the editor. Also, setting doc to undefined will not trigger an
|
|
||||||
update, so that you can clear it after setting one. */
|
|
||||||
export let doc: string;
|
|
||||||
|
|
||||||
/* Set this if you would like to listen to all transactions via `update` event. */
|
|
||||||
export let verbose = false;
|
|
||||||
|
|
||||||
/* Cached doc string so that we don't extract strings in bulk over and over. */
|
|
||||||
let _docCached: string = "";
|
|
||||||
|
|
||||||
/* Overwrite the bulk of the text with the one specified. */
|
|
||||||
function _setText(text: string) {
|
|
||||||
view.dispatch({
|
|
||||||
changes: { from: 0, to: view.state.doc.length, insert: text },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const subscribers = new Set();
|
|
||||||
|
|
||||||
/* And here comes the reactivity, implemented as a r/w store. */
|
|
||||||
export const docStore = {
|
|
||||||
ready: () => view !== null,
|
|
||||||
subscribe(cb) {
|
|
||||||
subscribers.add(cb);
|
|
||||||
|
|
||||||
if (!this.ready()) {
|
|
||||||
cb(null);
|
|
||||||
} else {
|
|
||||||
if (_docCached == null) {
|
|
||||||
_docCached = view.state.doc.toString();
|
|
||||||
}
|
|
||||||
cb(_docCached);
|
|
||||||
}
|
|
||||||
|
|
||||||
return () => void subscribers.delete(cb);
|
|
||||||
},
|
|
||||||
set(newValue: string) {
|
|
||||||
if (!_mounted) {
|
|
||||||
throw new Error(
|
|
||||||
"Cannot set docStore when the component is not mounted."
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const inited = _initEditorView(newValue);
|
|
||||||
if (!inited) _setText(newValue);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export let extensions = minimalSetup;
|
|
||||||
|
|
||||||
function _reconfigureExtensions() {
|
|
||||||
if (view === null) return;
|
|
||||||
view.dispatch({
|
|
||||||
effects: StateEffect.reconfigure.of(extensions),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
$: extensions, _reconfigureExtensions();
|
|
||||||
|
|
||||||
function _editorTxHandler(tr: any) {
|
|
||||||
this.update([tr]);
|
|
||||||
|
|
||||||
if (verbose) {
|
|
||||||
dispatch("update", tr);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tr.docChanged) {
|
|
||||||
_docCached = "";
|
|
||||||
if (subscribers.size) {
|
|
||||||
dispatchDocStore((_docCached = tr.newDoc.toString()));
|
|
||||||
}
|
|
||||||
dispatch("change", { view: this, tr });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function dispatchDocStore(s) {
|
|
||||||
for (const cb of subscribers) {
|
|
||||||
cb(s);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// the view will be inited with the either doc (as long as that it is not `undefined`)
|
|
||||||
// or the value in docStore once set
|
|
||||||
function _initEditorView(initialDoc: string) {
|
|
||||||
if (view !== null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
view = new EditorView({
|
|
||||||
doc: initialDoc,
|
|
||||||
extensions,
|
|
||||||
parent: dom,
|
|
||||||
dispatch: _editorTxHandler,
|
|
||||||
});
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
$: if (_mounted && doc !== undefined) {
|
|
||||||
_initEditorView(doc);
|
|
||||||
dispatchDocStore(doc);
|
|
||||||
}
|
|
||||||
|
|
||||||
onDestroy(() => {
|
|
||||||
if (view !== null) {
|
|
||||||
view.destroy();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="codemirror" bind:this="{dom}"></div>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.codemirror {
|
|
||||||
display: contents;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,29 +0,0 @@
|
||||||
<script lang="ts">
|
|
||||||
import Messages from "$lib/components/kratos/messages.svelte";
|
|
||||||
import type { Node } from "$lib/kratos/types";
|
|
||||||
|
|
||||||
export let node: Node;
|
|
||||||
|
|
||||||
const attr = node.attributes;
|
|
||||||
let labelText: string;
|
|
||||||
|
|
||||||
labelText = attr.name;
|
|
||||||
if (node.meta && node.meta.label) labelText = node.meta.label.text;
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<!-- -------------------------------------------------------------------------->
|
|
||||||
<fieldset>
|
|
||||||
<label>
|
|
||||||
<span>{labelText}</span>
|
|
||||||
<input
|
|
||||||
type="email"
|
|
||||||
name={attr.name}
|
|
||||||
value={attr.value ?? ""}
|
|
||||||
placeholder={labelText}
|
|
||||||
disabled={attr.disabled}
|
|
||||||
required={attr.required}
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
</fieldset>
|
|
||||||
|
|
||||||
<Messages messages={node.messages} />
|
|
|
@ -1,10 +0,0 @@
|
||||||
<script lang="ts">
|
|
||||||
import type { Node } from "$lib/kratos/types";
|
|
||||||
|
|
||||||
export let node: Node;
|
|
||||||
|
|
||||||
const attr = node.attributes;
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<!-- -------------------------------------------------------------------------->
|
|
||||||
<input type="hidden" name={attr.name} value={attr.value ?? ""} />
|
|
|
@ -1,74 +0,0 @@
|
||||||
<script lang="ts">
|
|
||||||
export let isHidden: boolean;
|
|
||||||
|
|
||||||
const toggleVisibility = () => {
|
|
||||||
isHidden = !isHidden;
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<!-- -------------------------------------------------------------------------->
|
|
||||||
<svg class="password-visibility-toggle" on:click={toggleVisibility}>
|
|
||||||
{#if isHidden}
|
|
||||||
<path
|
|
||||||
d="M8 2.36365
|
|
||||||
C 4.36364 2.36365 1.25818 4.62547 0 7.81819
|
|
||||||
C 1.25818 11.0109 4.36364 13.2727 8 13.2727
|
|
||||||
C 11.6364 13.2727 14.7418 11.0109 16 7.81819
|
|
||||||
C 14.7418 4.62547 11.6364 2.36365 8 2.36365
|
|
||||||
Z
|
|
||||||
M8 11.4546
|
|
||||||
C 5.99273 11.4546 4.36364 9.82547 4.36364 7.81819
|
|
||||||
C 4.36364 5.81092 5.99273 4.18183 8 4.18183
|
|
||||||
C 10.0073 4.18183 11.6364 5.81092 11.6364 7.81819
|
|
||||||
C 11.6364 9.82547 10.0073 11.4546 8 11.4546
|
|
||||||
Z
|
|
||||||
M8 5.63637
|
|
||||||
C 6.79273 5.63637 5.81818 6.61092 5.81818 7.81819
|
|
||||||
C 5.81818 9.02547 6.79273 10 8 10
|
|
||||||
C 9.20727 10 10.1818 9.02547 10.1818 7.81819
|
|
||||||
C 10.1818 6.61092 9.20727 5.63637 8 5.63637
|
|
||||||
Z"
|
|
||||||
/>
|
|
||||||
{:else}
|
|
||||||
<path
|
|
||||||
fill-rule="evenodd"
|
|
||||||
clip-rule="evenodd"
|
|
||||||
d="M14.8222 1.85355
|
|
||||||
C 15.0175 1.65829 15.0175 1.34171 14.8222 1.14645
|
|
||||||
C 14.627 0.951184 14.3104 0.951184 14.1151 1.14645
|
|
||||||
L 12.005 3.25653
|
|
||||||
C 10.8901 2.482 9.56509 1.92505 8.06 1.92505
|
|
||||||
C 3 1.92505 0 7.92505 0 7.92505
|
|
||||||
C 0 7.92505 1.16157 10.2482 3.25823 12.0033
|
|
||||||
L 1.19366 14.0679
|
|
||||||
C 0.998396 14.2632 0.998396 14.5798 1.19366 14.775
|
|
||||||
C 1.38892 14.9703 1.7055 14.9703 1.90076 14.775
|
|
||||||
L 14.8222 1.85355
|
|
||||||
Z
|
|
||||||
M 4.85879 10.4028
|
|
||||||
L 6.29159 8.96998
|
|
||||||
C 6.10643 8.66645 6 8.3089 6 7.92505
|
|
||||||
C 6 6.81505 6.89 5.92505 8 5.92505
|
|
||||||
C 8.38385 5.92505 8.7414 6.03148 9.04493 6.21664
|
|
||||||
L 10.4777 4.78384
|
|
||||||
C 9.79783 4.24654 8.93821 3.92505 8 3.92505
|
|
||||||
C 5.8 3.92505 4 5.72505 4 7.92505
|
|
||||||
C 4 8.86326 4.32149 9.72288 4.85879 10.4028
|
|
||||||
Z
|
|
||||||
M 11.8644 6.88906
|
|
||||||
L 13.8567 4.8968
|
|
||||||
C 15.2406 6.40616 16 7.92505 16 7.92505
|
|
||||||
C 16 7.92505 13 13.925 8.06 13.925
|
|
||||||
C 7.09599 13.925 6.20675 13.7073 5.39878 13.3547
|
|
||||||
L 6.96401 11.7895
|
|
||||||
C 7.29473 11.8779 7.64207 11.925 8 11.925
|
|
||||||
C 10.22 11.925 12 10.145 12 7.92505
|
|
||||||
C 12 7.56712 11.9529 7.21978 11.8644 6.88906
|
|
||||||
Z
|
|
||||||
M 9.33847 9.41501
|
|
||||||
L 9.48996 9.26352
|
|
||||||
C 9.44222 9.31669 9.39164 9.36726 9.33847 9.41501
|
|
||||||
Z"
|
|
||||||
/>
|
|
||||||
{/if}
|
|
||||||
</svg>
|
|
|
@ -1,26 +0,0 @@
|
||||||
<script lang="ts">
|
|
||||||
import Messages from "$lib/components/kratos/messages.svelte";
|
|
||||||
import PasswordToggle from "$lib/components/kratos/fieldset-password-toggle.svelte";
|
|
||||||
import type { Node } from "$lib/kratos/types";
|
|
||||||
|
|
||||||
export let node: Node;
|
|
||||||
|
|
||||||
const attr = node.attributes;
|
|
||||||
let isHidden = true;
|
|
||||||
let labelText: string;
|
|
||||||
|
|
||||||
labelText = attr.name;
|
|
||||||
if (node.meta && node.meta.label) labelText = node.meta.label.text;
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<!-- -------------------------------------------------------------------------->
|
|
||||||
<input
|
|
||||||
type={isHidden ? "password" : "text"}
|
|
||||||
name={attr.name}
|
|
||||||
value={attr.value ?? ""}
|
|
||||||
placeholder={labelText}
|
|
||||||
disabled={attr.disabled}
|
|
||||||
required={attr.required}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Messages messages={node.messages} />
|
|
|
@ -1,16 +0,0 @@
|
||||||
<script lang="ts">
|
|
||||||
import type { Node } from "$lib/kratos/types";
|
|
||||||
|
|
||||||
export let node: Node;
|
|
||||||
|
|
||||||
const attr = node.attributes;
|
|
||||||
let labelText: string;
|
|
||||||
|
|
||||||
labelText = "Submit";
|
|
||||||
if (node.meta && node.meta.label) labelText = node.meta.label.text;
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<!-- -------------------------------------------------------------------------->
|
|
||||||
<button type="submit" name={attr.name} value={attr.value}>
|
|
||||||
{labelText}
|
|
||||||
</button>
|
|
|
@ -1,24 +0,0 @@
|
||||||
<script lang="ts">
|
|
||||||
import Messages from "$lib/components/kratos/messages.svelte";
|
|
||||||
import type { Node } from "$lib/kratos/types";
|
|
||||||
|
|
||||||
export let node: Node;
|
|
||||||
|
|
||||||
const attr = node.attributes;
|
|
||||||
let labelText: string;
|
|
||||||
|
|
||||||
labelText = attr.name;
|
|
||||||
if (node.meta && node.meta.label) labelText = node.meta.label.text;
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<!-- -------------------------------------------------------------------------->
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
name={attr.name}
|
|
||||||
value={attr.value ?? ""}
|
|
||||||
placeholder={labelText}
|
|
||||||
disabled={attr.disabled}
|
|
||||||
required={attr.required}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Messages messages={node.messages} />
|
|
|
@ -1,27 +0,0 @@
|
||||||
<script lang="ts">
|
|
||||||
import Hidden from "$lib/components/kratos/fieldset-hidden.svelte";
|
|
||||||
import Password from "$lib/components/kratos/fieldset-password.svelte";
|
|
||||||
import Text from "$lib/components/kratos/fieldset-text.svelte";
|
|
||||||
import Email from "$lib/components/kratos/fieldset-email.svelte";
|
|
||||||
import Submit from "$lib/components/kratos/fieldset-submit.svelte";
|
|
||||||
import type { Node } from "$lib/kratos/types";
|
|
||||||
|
|
||||||
export let nodes: Node[];
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<!-- -------------------------------------------------------------------------->
|
|
||||||
{#each nodes as node}
|
|
||||||
{#if node.attributes.type === "hidden"}
|
|
||||||
<Hidden {node} />
|
|
||||||
{:else if node.attributes.type === "password"}
|
|
||||||
<Password {node} />
|
|
||||||
{:else if node.attributes.type === "text"}
|
|
||||||
<Text {node} />
|
|
||||||
{:else if node.attributes.type === "email"}
|
|
||||||
<Email {node} />
|
|
||||||
{:else if node.attributes.type === "submit"}
|
|
||||||
<Submit {node} />
|
|
||||||
{:else}
|
|
||||||
unknow type
|
|
||||||
{/if}
|
|
||||||
{/each}
|
|
|
@ -1,16 +0,0 @@
|
||||||
<script lang="ts">
|
|
||||||
import Fieldsets from "$lib/components/kratos/fieldsets.svelte";
|
|
||||||
import type { KratosForm } from "$lib/kratos/types";
|
|
||||||
|
|
||||||
export let dm: KratosForm;
|
|
||||||
export let groups: string[];
|
|
||||||
|
|
||||||
const nodes = dm.ui.nodes.filter(
|
|
||||||
(n) => n.type === "input" && groups.includes(n.group),
|
|
||||||
);
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<!-- -------------------------------------------------------------------------->
|
|
||||||
<form action={dm.ui.action} method={dm.ui.method}>
|
|
||||||
<Fieldsets {nodes} />
|
|
||||||
</form>
|
|
|
@ -1,14 +0,0 @@
|
||||||
<script lang="ts">
|
|
||||||
import type { Message } from "$lib/kratos/types";
|
|
||||||
|
|
||||||
export let messages: Message[];
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<!-- -------------------------------------------------------------------------->
|
|
||||||
{#if messages}
|
|
||||||
{#each messages as msg}
|
|
||||||
{msg.id} -
|
|
||||||
{msg.type} -
|
|
||||||
{msg.text}<br />
|
|
||||||
{/each}
|
|
||||||
{/if}
|
|
|
@ -1,2 +0,0 @@
|
||||||
export const KRATOS = "http://localhost:4433";
|
|
||||||
export const APP = "http://localhost:3000";
|
|
|
@ -1,11 +0,0 @@
|
||||||
export async function get(url: string) {
|
|
||||||
const res = await fetch(url, {
|
|
||||||
credentials: "include",
|
|
||||||
headers: {
|
|
||||||
"Accept": "application/json",
|
|
||||||
},
|
|
||||||
mode: "cors",
|
|
||||||
});
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
|
|
@ -1,82 +0,0 @@
|
||||||
import { browser } from "$app/environment";
|
|
||||||
import { KRATOS } from "$lib/config";
|
|
||||||
import { get } from "$lib/http";
|
|
||||||
import type {
|
|
||||||
KratosError,
|
|
||||||
KratosForm,
|
|
||||||
KratosIdentity,
|
|
||||||
KratosLogout,
|
|
||||||
} from "$lib/kratos/types";
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
|
||||||
export function getFlowId(urlSearch: string): string {
|
|
||||||
const qs = new URLSearchParams(urlSearch);
|
|
||||||
const flowId = qs.get("flow");
|
|
||||||
|
|
||||||
if (flowId) return flowId;
|
|
||||||
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
|
||||||
export async function getIdentity(): Promise<KratosIdentity> {
|
|
||||||
if (!browser) throw new Error("no browser environment");
|
|
||||||
|
|
||||||
const url = `${KRATOS}/sessions/whoami`;
|
|
||||||
const res = await get(url);
|
|
||||||
|
|
||||||
if (res.status !== 200) {
|
|
||||||
throw new Error("no identity");
|
|
||||||
}
|
|
||||||
|
|
||||||
const dm = await res.json();
|
|
||||||
return dm.identity;
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
|
||||||
export async function getDataModels(
|
|
||||||
flow: string,
|
|
||||||
flowId: string
|
|
||||||
): Promise<KratosForm | KratosError> {
|
|
||||||
if (!flowId) throw new Error("no flowId");
|
|
||||||
// if (!browser) throw new Error("no browser environment");
|
|
||||||
|
|
||||||
const url = `${KRATOS}/self-service/${flow}/flows?id=${flowId}`;
|
|
||||||
const res = await get(url);
|
|
||||||
const dm = await res.json();
|
|
||||||
|
|
||||||
if (dm.error) {
|
|
||||||
dm.instanceOf = "KratosError";
|
|
||||||
|
|
||||||
if (dm.error.details && dm.error.details.redirect_to) {
|
|
||||||
window.location.href = dm.error.details.redirect_to;
|
|
||||||
}
|
|
||||||
} else if (dm.ui) {
|
|
||||||
dm.instanceOf = "KratosForm";
|
|
||||||
} else {
|
|
||||||
throw new Error("unexpected Kratos object");
|
|
||||||
}
|
|
||||||
|
|
||||||
return dm;
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
|
||||||
export async function getLogoutDataModels(): Promise<
|
|
||||||
KratosLogout | KratosError
|
|
||||||
> {
|
|
||||||
if (!browser) throw new Error("no browser environment");
|
|
||||||
|
|
||||||
const url = `${KRATOS}/self-service/logout/browser`;
|
|
||||||
const res = await get(url);
|
|
||||||
const dm = await res.json();
|
|
||||||
|
|
||||||
if (dm.error) {
|
|
||||||
dm.instanceOf = "KratosError";
|
|
||||||
} else if (dm.logout_url) {
|
|
||||||
dm.instanceOf = "KratosLogout";
|
|
||||||
} else {
|
|
||||||
throw new Error("unexpected Kratos object");
|
|
||||||
}
|
|
||||||
|
|
||||||
return dm;
|
|
||||||
}
|
|
|
@ -1,109 +0,0 @@
|
||||||
// -----------------------------------------------------------------------------
|
|
||||||
export interface Attributes {
|
|
||||||
name: string;
|
|
||||||
type: string;
|
|
||||||
value?: string;
|
|
||||||
disabled: boolean;
|
|
||||||
required?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
|
||||||
export interface Label {
|
|
||||||
id: number;
|
|
||||||
type: string;
|
|
||||||
text: string;
|
|
||||||
context?: unknown;
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
|
||||||
export interface Message {
|
|
||||||
id: number;
|
|
||||||
type: string;
|
|
||||||
text: string;
|
|
||||||
context?: unknown;
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
|
||||||
export interface Meta {
|
|
||||||
label?: Label;
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
|
||||||
export interface Node {
|
|
||||||
type: string;
|
|
||||||
group: string;
|
|
||||||
attributes: Attributes;
|
|
||||||
messages: Message[];
|
|
||||||
meta: Meta;
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
|
||||||
export interface UI {
|
|
||||||
action: string;
|
|
||||||
method: string;
|
|
||||||
messages?: Message[];
|
|
||||||
nodes: Node[];
|
|
||||||
"updated_at": string;
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
|
||||||
export interface KratosForm {
|
|
||||||
instanceOf: "KratosForm";
|
|
||||||
id: string;
|
|
||||||
type: string;
|
|
||||||
forced?: boolean;
|
|
||||||
ui: UI;
|
|
||||||
"created_at"?: string;
|
|
||||||
"expires_at": string;
|
|
||||||
"issued_at": string;
|
|
||||||
"updated_at"?: string;
|
|
||||||
"request_url": string;
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
|
||||||
export interface KratosError {
|
|
||||||
instanceOf: "KratosError";
|
|
||||||
error: {
|
|
||||||
code: number;
|
|
||||||
message: string;
|
|
||||||
status: string;
|
|
||||||
reason?: string;
|
|
||||||
details?: {
|
|
||||||
docs: string;
|
|
||||||
hint: string;
|
|
||||||
"redirect_to": string;
|
|
||||||
"reject_reason": string;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
|
||||||
export interface KratosLogout {
|
|
||||||
instanceOf: "KratosLogout";
|
|
||||||
"logout_url": string;
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
|
||||||
export interface KratosIdentity {
|
|
||||||
id: string;
|
|
||||||
traits: {
|
|
||||||
email: string;
|
|
||||||
};
|
|
||||||
state: string;
|
|
||||||
"created_at": string;
|
|
||||||
"updated_at": string;
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
|
||||||
export interface KratosLoad {
|
|
||||||
status?: number;
|
|
||||||
redirect?: string;
|
|
||||||
props?: {
|
|
||||||
[key: string]:
|
|
||||||
| string
|
|
||||||
| KratosError
|
|
||||||
| KratosForm
|
|
||||||
| KratosIdentity
|
|
||||||
| KratosLogout;
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,4 +0,0 @@
|
||||||
import { writable } from "svelte/store";
|
|
||||||
import type { KratosIdentity } from "$lib/kratos/types";
|
|
||||||
|
|
||||||
export default writable({} as KratosIdentity);
|
|
|
@ -1,18 +0,0 @@
|
||||||
<script lang="ts">
|
|
||||||
import { browser } from "$app/environment";
|
|
||||||
import { get } from "svelte/store";
|
|
||||||
import identity from "$lib/stores/kratos/identity";
|
|
||||||
|
|
||||||
const _identity = get(identity);
|
|
||||||
|
|
||||||
if (browser) {
|
|
||||||
if (_identity.id) {
|
|
||||||
console.log("signed in");
|
|
||||||
} else {
|
|
||||||
console.log("not signed in");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<!-- -------------------------------------------------------------------------->
|
|
||||||
<slot />
|
|
|
@ -1,16 +0,0 @@
|
||||||
import { getIdentity } from "$lib/kratos";
|
|
||||||
import identity from "$lib/stores/kratos/identity";
|
|
||||||
|
|
||||||
// ---------------------------Q--------------------------------------------------
|
|
||||||
export const csr = true;
|
|
||||||
export const prerender = false;
|
|
||||||
|
|
||||||
export async function load() {
|
|
||||||
await getIdentity()
|
|
||||||
.then((_identity) => {
|
|
||||||
identity.set(_identity);
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
//no identity
|
|
||||||
});
|
|
||||||
}
|
|
|
@ -1,17 +0,0 @@
|
||||||
<script lang="ts">
|
|
||||||
import { APP } from "$lib/config";
|
|
||||||
import { get } from "svelte/store";
|
|
||||||
import identity from "$lib/stores/kratos/identity";
|
|
||||||
|
|
||||||
const _identity = get(identity);
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<section id="welcome">
|
|
||||||
<h1>Probo</h1>
|
|
||||||
|
|
||||||
{#if !_identity.id}
|
|
||||||
<p><a href="{APP}/auth/login">Login</a></p>
|
|
||||||
{:else}
|
|
||||||
<p><a href="{APP}/dashboard">Dashboard</a></p>
|
|
||||||
{/if}
|
|
||||||
</section>
|
|
|
@ -1,39 +0,0 @@
|
||||||
<script lang="ts">
|
|
||||||
import { APP, KRATOS } from "$lib/config";
|
|
||||||
import { page } from "$app/stores";
|
|
||||||
import { browser } from "$app/environment";
|
|
||||||
import { getFlowId, getDataModels } from "$lib/kratos";
|
|
||||||
import Form from "$lib/components/kratos/form.svelte";
|
|
||||||
import Messages from "$lib/components/kratos/messages.svelte";
|
|
||||||
|
|
||||||
// const flowId = getFlowId($page.url.search); -->
|
|
||||||
// if (browser && !flowId) -->
|
|
||||||
// window.location.href = `${KRATOS}/self-service/login/browser`; -->
|
|
||||||
|
|
||||||
// const pr = getDataModels("login", flowId); -->
|
|
||||||
|
|
||||||
export let data: PageData;
|
|
||||||
$: pr = data.pr;
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<!-- -------------------------------------------------------------------------->
|
|
||||||
<div id="login">
|
|
||||||
{#await data.pr then dm} {#if dm.instanceOf === "KratosForm"}
|
|
||||||
<div id="login">
|
|
||||||
<h2>Sign in</h2>
|
|
||||||
|
|
||||||
{#if dm.ui.messages}
|
|
||||||
<Messages messages="{dm.ui.messages}" />
|
|
||||||
{/if} <Form {dm} groups={["default", "password"]} />
|
|
||||||
|
|
||||||
<hr />
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<p><a href="{APP}/auth/recovery">Forget password?</a></p>
|
|
||||||
<p><a href="{APP}/auth/registration">Don't have an account?</a></p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{:else}
|
|
||||||
<p>Something went wrong</p>
|
|
||||||
{/if} {/await}
|
|
||||||
</div>
|
|
|
@ -1,16 +0,0 @@
|
||||||
import { getFlowId, getDataModels } from "$lib/kratos";
|
|
||||||
import { page } from "$app/stores";
|
|
||||||
import { browser } from "$app/environment";
|
|
||||||
import { KRATOS } from "$lib/config";
|
|
||||||
|
|
||||||
/** @type {import('./$types').PageLoad} */
|
|
||||||
export async function load({ url }) {
|
|
||||||
const flowId = getFlowId(url.search);
|
|
||||||
|
|
||||||
if (browser && !flowId)
|
|
||||||
window.location.href = `${KRATOS}/self-service/login/browser`;
|
|
||||||
|
|
||||||
const pr = getDataModels("login", flowId);
|
|
||||||
|
|
||||||
return { pr: pr };
|
|
||||||
}
|
|
|
@ -1,15 +0,0 @@
|
||||||
import { browser } from "$app/environment";
|
|
||||||
import { getLogoutDataModels } from "$lib/kratos";
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
|
||||||
export async function load() {
|
|
||||||
if (!browser) return {};
|
|
||||||
|
|
||||||
const dm = await getLogoutDataModels();
|
|
||||||
|
|
||||||
if (dm.instanceOf === "KratosLogout") {
|
|
||||||
window.location.replace(dm.logout_url);
|
|
||||||
} else {
|
|
||||||
window.location.replace("/");
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,33 +0,0 @@
|
||||||
<script lang="ts">
|
|
||||||
import { KRATOS } from "$lib/config";
|
|
||||||
import { page } from "$app/stores";
|
|
||||||
import { browser } from "$app/environment";
|
|
||||||
import { getFlowId, getDataModels } from "$lib/kratos";
|
|
||||||
import Form from "$lib/components/kratos/form.svelte";
|
|
||||||
import Messages from "$lib/components/kratos/messages.svelte";
|
|
||||||
|
|
||||||
const flowId = getFlowId($page.url.search);
|
|
||||||
if (browser && !flowId)
|
|
||||||
window.location.href = `${KRATOS}/self-service/recovery/browser`;
|
|
||||||
|
|
||||||
const pr = getDataModels("recovery", flowId);
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<!-- -------------------------------------------------------------------------->
|
|
||||||
<section id="recovery">
|
|
||||||
{#await pr then dm}
|
|
||||||
{#if dm.instanceOf === "KratosForm"}
|
|
||||||
<div class="container" id="recovery">
|
|
||||||
<h2 class="subheading">recovery</h2>
|
|
||||||
|
|
||||||
{#if dm.ui.messages}
|
|
||||||
<Messages messages={dm.ui.messages} />
|
|
||||||
{:else}
|
|
||||||
<Form {dm} groups={["default", "link"]} />
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
{:else}
|
|
||||||
<p>Something went wrong</p>
|
|
||||||
{/if}
|
|
||||||
{/await}
|
|
||||||
</section>
|
|
|
@ -1,39 +0,0 @@
|
||||||
<script lang="ts">
|
|
||||||
import { APP, KRATOS } from "$lib/config";
|
|
||||||
import { page } from "$app/stores";
|
|
||||||
import { browser } from "$app/environment";
|
|
||||||
import { getFlowId, getDataModels } from "$lib/kratos";
|
|
||||||
import Form from "$lib/components/kratos/form.svelte";
|
|
||||||
import Messages from "$lib/components/kratos/messages.svelte";
|
|
||||||
|
|
||||||
const flowId = getFlowId($page.url.search);
|
|
||||||
|
|
||||||
if (browser && !flowId)
|
|
||||||
window.location.href = `${KRATOS}/self-service/registration/browser`;
|
|
||||||
|
|
||||||
const pr = getDataModels("registration", flowId);
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<!-- -------------------------------------------------------------------------->
|
|
||||||
<section id="registration">
|
|
||||||
{#await pr then dm}
|
|
||||||
{#if dm.instanceOf === "KratosForm"}
|
|
||||||
<div class="container" id="registration">
|
|
||||||
<h2 class="subheading">Registration</h2>
|
|
||||||
|
|
||||||
{#if dm.ui.messages}
|
|
||||||
<Messages messages={dm.ui.messages} />
|
|
||||||
{/if}
|
|
||||||
<Form {dm} groups={["default", "password"]} />
|
|
||||||
|
|
||||||
<hr class="divider" />
|
|
||||||
|
|
||||||
<section class="alternative-actions">
|
|
||||||
<p><a href="{APP}/login">Already have an account?</a></p>
|
|
||||||
</section>
|
|
||||||
</div>
|
|
||||||
{:else}
|
|
||||||
<p>Something went wrong</p>
|
|
||||||
{/if}
|
|
||||||
{/await}
|
|
||||||
</section>
|
|
|
@ -1,35 +0,0 @@
|
||||||
<script lang="ts">
|
|
||||||
import { KRATOS } from "$lib/config";
|
|
||||||
import { page } from "$app/stores";
|
|
||||||
import { browser } from "$app/environment";
|
|
||||||
import { getFlowId, getDataModels } from "$lib/kratos";
|
|
||||||
import Form from "$lib/components/kratos/form.svelte";
|
|
||||||
import Messages from "$lib/components/kratos/messages.svelte";
|
|
||||||
|
|
||||||
const flowId = getFlowId($page.url.search);
|
|
||||||
if (browser && !flowId)
|
|
||||||
window.location.href = `${KRATOS}/self-service/settings/browser`;
|
|
||||||
|
|
||||||
const pr = getDataModels("settings", flowId);
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<!-- -------------------------------------------------------------------------->
|
|
||||||
<section id="settings">
|
|
||||||
{#await pr then dm}
|
|
||||||
{#if dm.instanceOf === "KratosForm"}
|
|
||||||
<div class="container" id="settings">
|
|
||||||
<h2 class="subheading">Settings</h2>
|
|
||||||
|
|
||||||
{#if dm.ui.messages}
|
|
||||||
<Messages messages={dm.ui.messages} />
|
|
||||||
{:else}
|
|
||||||
<Form {dm} groups={["default", "profile"]} />
|
|
||||||
<hr class="divider" />
|
|
||||||
<Form {dm} groups={["default", "password"]} />
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
{:else}
|
|
||||||
<p>Something went wrong</p>
|
|
||||||
{/if}
|
|
||||||
{/await}
|
|
||||||
</section>
|
|
|
@ -1,42 +0,0 @@
|
||||||
<script lang="ts">
|
|
||||||
import { KRATOS } from "$lib/config";
|
|
||||||
import { page } from "$app/stores";
|
|
||||||
import { browser } from "$app/environment";
|
|
||||||
import { get } from "svelte/store";
|
|
||||||
import { getFlowId, getDataModels } from "$lib/kratos";
|
|
||||||
import identity from "$lib/stores/kratos/identity";
|
|
||||||
import Form from "$lib/components/kratos/form.svelte";
|
|
||||||
import Messages from "$lib/components/kratos/messages.svelte";
|
|
||||||
|
|
||||||
const _identity = get(identity);
|
|
||||||
const flowId = getFlowId($page.url.search);
|
|
||||||
|
|
||||||
if (browser) {
|
|
||||||
if (!_identity) {
|
|
||||||
window.location.href = `${KRATOS}/self-service/login/browser`;
|
|
||||||
} else if (!flowId) {
|
|
||||||
window.location.href = `${KRATOS}/self-service/verification/browser`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const pr = getDataModels("verification", flowId);
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<!-- -------------------------------------------------------------------------->
|
|
||||||
<section id="verification">
|
|
||||||
{#await pr then dm}
|
|
||||||
{#if dm.instanceOf === "KratosForm"}
|
|
||||||
<div class="container" id="verification">
|
|
||||||
<h2 class="subheading">verification</h2>
|
|
||||||
|
|
||||||
{#if dm.ui.messages}
|
|
||||||
<Messages messages={dm.ui.messages} />
|
|
||||||
{:else}
|
|
||||||
<Form {dm} groups={["default", "link"]} />
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
{:else}
|
|
||||||
<p>Something went wrong</p>
|
|
||||||
{/if}
|
|
||||||
{/await}
|
|
||||||
</section>
|
|
|
@ -1,16 +0,0 @@
|
||||||
<script lang="ts">
|
|
||||||
import { browser } from "$app/environment";
|
|
||||||
import { KRATOS } from "$lib/config";
|
|
||||||
import { get } from "svelte/store";
|
|
||||||
import identity from "$lib/stores/kratos/identity";
|
|
||||||
|
|
||||||
const _identity = get(identity);
|
|
||||||
|
|
||||||
if (browser && !_identity.id) {
|
|
||||||
const loginUrl = `${KRATOS}/self-service/login/browser`;
|
|
||||||
window.location.replace(loginUrl);
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<!-- -------------------------------------------------------------------------->
|
|
||||||
<slot />
|
|
|
@ -1,29 +0,0 @@
|
||||||
import { get } from "svelte/store";
|
|
||||||
import identity from "$lib/stores/kratos/identity";
|
|
||||||
|
|
||||||
type Question = {
|
|
||||||
text: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
type Answer = {
|
|
||||||
text: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
type Quiz = {
|
|
||||||
uid: string;
|
|
||||||
question: Question;
|
|
||||||
answers: Answer[];
|
|
||||||
};
|
|
||||||
|
|
||||||
/** @type {import('./$types').PageLoad} */
|
|
||||||
export async function load() {
|
|
||||||
const _identity = get(identity);
|
|
||||||
if (_identity) {
|
|
||||||
const res = await fetch("http://localhost:8080/quizzes");
|
|
||||||
const response = await res.json();
|
|
||||||
|
|
||||||
if (response.status === "success") {
|
|
||||||
return { quizzes: (await response.content) as Quiz[] };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,42 +0,0 @@
|
||||||
<script lang="ts">
|
|
||||||
import { APP } from "$lib/config";
|
|
||||||
// import { get } from "svelte/store";
|
|
||||||
// import identity from "$lib/stores/kratos/identity";
|
|
||||||
import CodeMirror, { basicSetup } from "$lib/components/codemirror/codemirror.svelte";
|
|
||||||
import type { PageData } from './$types';
|
|
||||||
|
|
||||||
export let data: PageData;
|
|
||||||
$: quizzes = data.quizzes;
|
|
||||||
|
|
||||||
// const _identity = get(identity);
|
|
||||||
let email = "";
|
|
||||||
let store: any;
|
|
||||||
|
|
||||||
// if (_identity && _identity.traits) email = _identity.traits.email;
|
|
||||||
|
|
||||||
function changeHandler({ detail: {tr: any} }) {
|
|
||||||
console.log('change', tr.changes.toJSON())
|
|
||||||
}
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<section id="dashboard">
|
|
||||||
<h2>Probo Dashboard</h2>
|
|
||||||
|
|
||||||
<p>Hello {email}</p>
|
|
||||||
|
|
||||||
{#each quizzes as quiz}
|
|
||||||
<ul>
|
|
||||||
<li>{quiz.Question.Text}</li>
|
|
||||||
</ul>
|
|
||||||
{/each}
|
|
||||||
|
|
||||||
<CodeMirror doc={'Testo della domanda\n\n* Risposta 1\n* Risposta 2\n* Risposta 3'}
|
|
||||||
bind:docStore={store}
|
|
||||||
extensions={basicSetup}
|
|
||||||
on:change={changeHandler}>
|
|
||||||
</CodeMirror>
|
|
||||||
|
|
||||||
<p><a href="{APP}/auth/settings">Settings</a></p>
|
|
||||||
<p><a href="{APP}/auth/logout">Logout</a></p>
|
|
||||||
</section>
|
|
Binary file not shown.
Before Width: | Height: | Size: 1.5 KiB |
|
@ -1,16 +0,0 @@
|
||||||
//import adapter from '@sveltejs/adapter-auto';
|
|
||||||
import adapter from "@sveltejs/adapter-node";
|
|
||||||
import preprocess from "svelte-preprocess";
|
|
||||||
|
|
||||||
/** @type {import('@sveltejs/kit').Config} */
|
|
||||||
const config = {
|
|
||||||
// Consult https://github.com/sveltejs/svelte-preprocess
|
|
||||||
// for more information about preprocessors
|
|
||||||
preprocess: preprocess(),
|
|
||||||
|
|
||||||
kit: {
|
|
||||||
adapter: adapter(),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export default config;
|
|
|
@ -1,26 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# Set Session Name
|
|
||||||
SESSION="kratos-svelte-login"
|
|
||||||
SESSIONEXISTS=$(tmux list-sessions | grep $SESSION)
|
|
||||||
|
|
||||||
# Only create tmux session if it doesn't already exist
|
|
||||||
if [ "$SESSIONEXISTS" = "" ]
|
|
||||||
then
|
|
||||||
# Start New Session with our name
|
|
||||||
tmux new-session -d -s $SESSION
|
|
||||||
|
|
||||||
# Name first Pane and start zsh
|
|
||||||
tmux rename-window -t 0 'src'
|
|
||||||
|
|
||||||
# Create and setup pane for running the backend
|
|
||||||
# tmux send-keys -t 'Main' 'bash' C-m 'clear' C-m 'cd backend && go build -o backend . && ./backend' C-m
|
|
||||||
tmux send-keys -t 'src' 'cd src && npm run dev -- --host --port 3000' C-m
|
|
||||||
|
|
||||||
# Create an horizontal pane for terminal commands
|
|
||||||
tmux split-window -vf -l 1
|
|
||||||
tmux send-keys 'emacs src/routes/+page.svelte &' Enter
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Attach Session, on the Main window
|
|
||||||
tmux attach-session -t $SESSION:0
|
|
|
@ -1,18 +0,0 @@
|
||||||
{
|
|
||||||
"extends": "./.svelte-kit/tsconfig.json",
|
|
||||||
"compilerOptions": {
|
|
||||||
"allowJs": true,
|
|
||||||
"checkJs": true,
|
|
||||||
"esModuleInterop": true,
|
|
||||||
"forceConsistentCasingInFileNames": true,
|
|
||||||
"resolveJsonModule": true,
|
|
||||||
"skipLibCheck": true,
|
|
||||||
"sourceMap": true,
|
|
||||||
"strict": true
|
|
||||||
}
|
|
||||||
// Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias
|
|
||||||
//
|
|
||||||
// If you want to overwrite includes/excludes, make sure to copy over the
|
|
||||||
// relevant includes/excludes from the referenced tsconfig.json
|
|
||||||
// TypeScript does not merge them in
|
|
||||||
}
|
|
|
@ -1,13 +0,0 @@
|
||||||
import { sveltekit } from "@sveltejs/kit/vite";
|
|
||||||
import type { UserConfig } from "vite";
|
|
||||||
|
|
||||||
const config: UserConfig = {
|
|
||||||
plugins: [sveltekit()],
|
|
||||||
server: {
|
|
||||||
hmr: {
|
|
||||||
clientPort: 3000,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export default config;
|
|
3
main.go
3
main.go
|
@ -5,6 +5,7 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"git.andreafazzi.eu/andrea/probo/hasher/sha256"
|
"git.andreafazzi.eu/andrea/probo/hasher/sha256"
|
||||||
|
"git.andreafazzi.eu/andrea/probo/logger"
|
||||||
"git.andreafazzi.eu/andrea/probo/store/memory"
|
"git.andreafazzi.eu/andrea/probo/store/memory"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
@ -12,7 +13,7 @@ import (
|
||||||
const port = "8080"
|
const port = "8080"
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
// logger.SetLevel(logger.DebugLevel)
|
logger.SetLevel(logger.DebugLevel)
|
||||||
|
|
||||||
server := NewProboCollectorServer(
|
server := NewProboCollectorServer(
|
||||||
memory.NewMemoryProboCollectorStore(
|
memory.NewMemoryProboCollectorStore(
|
||||||
|
|
21
misc/logseq/LICENSE
Normal file
21
misc/logseq/LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2022 Andrea Fazzi
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
|
@ -2,6 +2,7 @@ package models
|
||||||
|
|
||||||
type Quiz struct {
|
type Quiz struct {
|
||||||
ID string
|
ID string
|
||||||
|
Hash string
|
||||||
Question *Question
|
Question *Question
|
||||||
Answers []*Answer
|
Answers []*Answer
|
||||||
Correct *Answer
|
Correct *Answer
|
||||||
|
|
BIN
probo
Executable file
BIN
probo
Executable file
Binary file not shown.
|
@ -10,7 +10,6 @@ import (
|
||||||
"git.andreafazzi.eu/andrea/probo/models"
|
"git.andreafazzi.eu/andrea/probo/models"
|
||||||
"git.andreafazzi.eu/andrea/probo/store"
|
"git.andreafazzi.eu/andrea/probo/store"
|
||||||
"github.com/julienschmidt/httprouter"
|
"github.com/julienschmidt/httprouter"
|
||||||
"log"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type ProboCollectorServer struct {
|
type ProboCollectorServer struct {
|
||||||
|
@ -101,7 +100,6 @@ func (ps *ProboCollectorServer) updateQuizHandler(w http.ResponseWriter, r *http
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ps *ProboCollectorServer) readAllQuiz(w http.ResponseWriter, r *http.Request) ([]*models.Quiz, error) {
|
func (ps *ProboCollectorServer) readAllQuiz(w http.ResponseWriter, r *http.Request) ([]*models.Quiz, error) {
|
||||||
log.Println(r)
|
|
||||||
quizzes, err := ps.store.ReadAllQuizzes()
|
quizzes, err := ps.store.ReadAllQuizzes()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
7
store/file/.md
Normal file
7
store/file/.md
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
Newly created question text.
|
||||||
|
|
||||||
|
* Answer 1
|
||||||
|
* Answer 1
|
||||||
|
* Answer 2
|
||||||
|
* Answer 3
|
||||||
|
* Answer 4
|
162
store/file/file.go
Normal file
162
store/file/file.go
Normal file
|
@ -0,0 +1,162 @@
|
||||||
|
package file
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io/fs"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"git.andreafazzi.eu/andrea/probo/client"
|
||||||
|
"git.andreafazzi.eu/andrea/probo/hasher/sha256"
|
||||||
|
"git.andreafazzi.eu/andrea/probo/models"
|
||||||
|
"git.andreafazzi.eu/andrea/probo/store/memory"
|
||||||
|
)
|
||||||
|
|
||||||
|
type FileProboCollectorStore struct {
|
||||||
|
Dir string
|
||||||
|
|
||||||
|
memoryStore *memory.MemoryProboCollectorStore
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewFileProboCollectorStore(dirname string) (*FileProboCollectorStore, error) {
|
||||||
|
s := new(FileProboCollectorStore)
|
||||||
|
|
||||||
|
files, err := ioutil.ReadDir(dirname)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
markdownFiles := make([]fs.FileInfo, 0)
|
||||||
|
|
||||||
|
for _, file := range files {
|
||||||
|
filename := file.Name()
|
||||||
|
if !file.IsDir() && strings.HasSuffix(filename, ".md") {
|
||||||
|
markdownFiles = append(markdownFiles, file)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(markdownFiles) == 0 {
|
||||||
|
return nil, fmt.Errorf("The directory is empty.")
|
||||||
|
}
|
||||||
|
|
||||||
|
s.memoryStore = memory.NewMemoryProboCollectorStore(
|
||||||
|
sha256.NewDefault256Hasher(sha256.DefaultSHA256HashingFn),
|
||||||
|
)
|
||||||
|
|
||||||
|
for _, file := range markdownFiles {
|
||||||
|
filename := file.Name()
|
||||||
|
fullPath := filepath.Join(dirname, filename)
|
||||||
|
|
||||||
|
content, err := os.ReadFile(fullPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
quiz, err := QuizFromMarkdown(string(content))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
s.memoryStore.CreateQuiz(&client.CreateUpdateQuizRequest{
|
||||||
|
Quiz: quiz,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
s.Dir = dirname
|
||||||
|
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *FileProboCollectorStore) ReadAllQuizzes() ([]*models.Quiz, error) {
|
||||||
|
return s.memoryStore.ReadAllQuizzes()
|
||||||
|
}
|
||||||
|
func (s *FileProboCollectorStore) CreateQuiz(r *client.CreateUpdateQuizRequest) (*models.Quiz, error) {
|
||||||
|
quiz, err := s.memoryStore.CreateQuiz(r)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = s.writeMarkdownFile(quiz)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return quiz, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func MarkdownFromQuiz(quiz *models.Quiz) (string, error) {
|
||||||
|
if quiz.Question == nil {
|
||||||
|
return "", errors.New("Quiz should contain a question but it wasn't provided.")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(quiz.Answers) == 0 {
|
||||||
|
return "", errors.New("Quiz should contain at least 2 answers but none was provided.")
|
||||||
|
}
|
||||||
|
|
||||||
|
if quiz.Correct == nil {
|
||||||
|
return "", errors.New("Quiz should contain a correct answer but non was provided.")
|
||||||
|
}
|
||||||
|
|
||||||
|
correctAnswer := "* " + quiz.Correct.Text
|
||||||
|
var otherAnswers string
|
||||||
|
|
||||||
|
for _, answer := range quiz.Answers {
|
||||||
|
otherAnswers += "* " + answer.Text + "\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
markdown := quiz.Question.Text + "\n\n" + correctAnswer + "\n" + otherAnswers
|
||||||
|
|
||||||
|
return markdown, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func QuizFromMarkdown(markdown string) (*client.Quiz, error) {
|
||||||
|
lines := strings.Split(markdown, "\n")
|
||||||
|
|
||||||
|
questionText := ""
|
||||||
|
answers := []*client.Answer{}
|
||||||
|
|
||||||
|
for _, line := range lines {
|
||||||
|
if strings.HasPrefix(line, "*") {
|
||||||
|
answerText := strings.TrimPrefix(line, "* ")
|
||||||
|
correct := len(answers) == 0
|
||||||
|
answer := &client.Answer{Text: answerText, Correct: correct}
|
||||||
|
answers = append(answers, answer)
|
||||||
|
} else {
|
||||||
|
if questionText != "" {
|
||||||
|
questionText += "\n"
|
||||||
|
}
|
||||||
|
questionText += line
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
questionText = strings.TrimRight(questionText, "\n")
|
||||||
|
|
||||||
|
if questionText == "" {
|
||||||
|
return nil, fmt.Errorf("Question text should not be empty.")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(answers) < 2 {
|
||||||
|
return nil, fmt.Errorf("Number of answers should be at least 2 but parsed answers are %d.", len(answers))
|
||||||
|
}
|
||||||
|
|
||||||
|
question := &client.Question{Text: questionText}
|
||||||
|
quiz := &client.Quiz{Question: question, Answers: answers}
|
||||||
|
|
||||||
|
return quiz, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *FileProboCollectorStore) writeMarkdownFile(quiz *models.Quiz) error {
|
||||||
|
markdown, err := MarkdownFromQuiz(quiz)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
filename := filepath.Join(s.Dir, quiz.Hash+".md")
|
||||||
|
err = ioutil.WriteFile(filename, []byte(markdown), 0644)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
114
store/file/file_test.go
Normal file
114
store/file/file_test.go
Normal file
|
@ -0,0 +1,114 @@
|
||||||
|
package file
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"git.andreafazzi.eu/andrea/probo/client"
|
||||||
|
"git.andreafazzi.eu/andrea/probo/models"
|
||||||
|
"github.com/remogatto/prettytest"
|
||||||
|
)
|
||||||
|
|
||||||
|
type testSuite struct {
|
||||||
|
prettytest.Suite
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRunner(t *testing.T) {
|
||||||
|
prettytest.Run(
|
||||||
|
t,
|
||||||
|
new(testSuite),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *testSuite) TestQuizFromMarkdown() {
|
||||||
|
markdown := `Question text (1).
|
||||||
|
|
||||||
|
Question text (2).
|
||||||
|
|
||||||
|
Question text (3).
|
||||||
|
|
||||||
|
* Answer 1
|
||||||
|
* Answer 2
|
||||||
|
* Answer 3
|
||||||
|
* Answer 4`
|
||||||
|
|
||||||
|
expectedQuiz := &client.Quiz{
|
||||||
|
Question: &client.Question{Text: "Question text (1).\n\nQuestion text (2).\n\nQuestion text (3)."},
|
||||||
|
Answers: []*client.Answer{
|
||||||
|
{Text: "Answer 1", Correct: true},
|
||||||
|
{Text: "Answer 2", Correct: false},
|
||||||
|
{Text: "Answer 3", Correct: false},
|
||||||
|
{Text: "Answer 4", Correct: false},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
quiz, err := QuizFromMarkdown(markdown)
|
||||||
|
t.Nil(err, fmt.Sprintf("Quiz should be parsed without errors: %v", err))
|
||||||
|
|
||||||
|
if !t.Failed() {
|
||||||
|
t.True(reflect.DeepEqual(quiz, expectedQuiz), fmt.Sprintf("Expected %+v, got %+v", expectedQuiz, quiz))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *testSuite) TestReadAllQuizzes() {
|
||||||
|
store, err := NewFileProboCollectorStore("./test/quizzes")
|
||||||
|
t.True(err == nil, fmt.Sprintf("A file store should be initialized without problems but an error occurred: %v", err))
|
||||||
|
|
||||||
|
if !t.Failed() {
|
||||||
|
result, err := store.ReadAllQuizzes()
|
||||||
|
|
||||||
|
t.True(err == nil, fmt.Sprintf("Quizzes should be returned without errors: %v", err))
|
||||||
|
|
||||||
|
if !t.Failed() {
|
||||||
|
t.Equal(
|
||||||
|
2,
|
||||||
|
len(result),
|
||||||
|
fmt.Sprintf("The store contains 3 files but only 2 should be parsed (duplicated quiz). Total of parsed quizzes are instead %v", len(result)),
|
||||||
|
)
|
||||||
|
t.Equal("Question text 1.", result[0].Question.Text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *testSuite) TestCreateQuiz() {
|
||||||
|
dirname := "./test/quizzes"
|
||||||
|
store, err := NewFileProboCollectorStore(dirname)
|
||||||
|
|
||||||
|
t.True(err == nil, fmt.Sprintf("A file store should be initialized without problems but an error occurred: %v", err))
|
||||||
|
|
||||||
|
_, err = store.CreateQuiz(
|
||||||
|
&client.CreateUpdateQuizRequest{
|
||||||
|
Quiz: &client.Quiz{
|
||||||
|
Question: &client.Question{Text: "Newly created question text."},
|
||||||
|
Answers: []*client.Answer{
|
||||||
|
{Text: "Answer 1", Correct: true},
|
||||||
|
{Text: "Answer 2", Correct: false},
|
||||||
|
{Text: "Answer 3", Correct: false},
|
||||||
|
{Text: "Answer 4", Correct: false},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Nil(err, fmt.Sprintf("An error was raised when saving the quiz on disk: %v", err))
|
||||||
|
|
||||||
|
newFilename := filepath.Join(
|
||||||
|
dirname,
|
||||||
|
"94ed4e9cdf8e0a75a2c5ce925cb791ebc5977ce1801e12059f58ce4d66c0c7f6.md",
|
||||||
|
)
|
||||||
|
exists, err := os.Stat(newFilename)
|
||||||
|
t.Nil(err, "Stat should not return an error")
|
||||||
|
|
||||||
|
if !t.Failed() {
|
||||||
|
t.True(exists != nil, "The new quiz file was not created.")
|
||||||
|
err := os.Remove(newFilename)
|
||||||
|
t.Nil(err, "Stat should not return an error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testsAreEqual(got, want []*models.Quiz) bool {
|
||||||
|
return reflect.DeepEqual(got, want)
|
||||||
|
}
|
5
store/file/test/quizzes/quiz_1.md
Normal file
5
store/file/test/quizzes/quiz_1.md
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
Question text 1.
|
||||||
|
|
||||||
|
* Answer 1
|
||||||
|
* Answer 2
|
||||||
|
* Answer 3
|
7
store/file/test/quizzes/quiz_2.md
Normal file
7
store/file/test/quizzes/quiz_2.md
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
Question text 2.
|
||||||
|
|
||||||
|
* Answer 1
|
||||||
|
* Answer 2
|
||||||
|
* Answer 3
|
||||||
|
* Answer 4
|
||||||
|
|
7
store/file/test/quizzes/quiz_3.md
Normal file
7
store/file/test/quizzes/quiz_3.md
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
Question text 2.
|
||||||
|
|
||||||
|
* Answer 1
|
||||||
|
* Answer 2
|
||||||
|
* Answer 3
|
||||||
|
* Answer 4
|
||||||
|
|
|
@ -90,6 +90,7 @@ func (s *MemoryProboCollectorStore) createQuizFromHash(id string, hash string, q
|
||||||
defer s.lock.Unlock()
|
defer s.lock.Unlock()
|
||||||
|
|
||||||
quiz.ID = id
|
quiz.ID = id
|
||||||
|
quiz.Hash = hash
|
||||||
|
|
||||||
s.quizzesHashes[hash] = quiz
|
s.quizzesHashes[hash] = quiz
|
||||||
s.quizzes[id] = quiz
|
s.quizzes[id] = quiz
|
||||||
|
|
Loading…
Reference in a new issue