├── .env.example
├── .eslintrc.cjs
├── .gitignore
├── .kratos
├── identity.schema.json
├── kratos.yml
└── oidc
│ ├── oidc.facebook.jsonnet
│ └── oidc.google.jsonnet
├── .npmrc
├── .oathkeeper
├── access_rules.yml
└── oathkeeper.yml
├── .prettierignore
├── .prettierrc
├── Dockerfile
├── README.md
├── docker-compose-oathkeeper.yml
├── docker-compose.yml
├── nginx.conf
├── package.json
├── pnpm-lock.yaml
├── postcss.config.cjs
├── src
├── app.html
├── global.css
├── global.d.ts
├── hooks
│ └── index.ts
├── lib
│ ├── Confirm.svelte
│ ├── InlineSvg.svelte
│ ├── Kratos
│ │ ├── AuthContainer.svelte
│ │ ├── AuthForm.svelte
│ │ ├── FormSectionHeader.svelte
│ │ ├── InputDefault.svelte
│ │ ├── InputHidden.svelte
│ │ ├── InputPassword.svelte
│ │ ├── Message.svelte
│ │ ├── PasswordToggle.svelte
│ │ └── SubmitButton.svelte
│ ├── Link.svelte
│ ├── Nav.svelte
│ ├── config.ts
│ ├── helpers.ts
│ ├── services
│ │ └── kratos.ts
│ └── types.ts
└── routes
│ ├── __error.svelte
│ ├── __layout.svelte
│ ├── _load.ts
│ ├── about.svelte
│ ├── api
│ └── auth
│ │ ├── [...auth].ts
│ │ └── delete.ts
│ ├── auth
│ ├── _load.ts
│ ├── login.svelte
│ ├── recovery.svelte
│ ├── registration.svelte
│ └── verify.svelte
│ ├── error.svelte
│ ├── index.svelte
│ ├── profile
│ ├── @[user]
│ │ └── index.svelte
│ └── [user].svelte
│ └── settings.svelte
├── static
├── favicon.png
├── images
│ ├── 404.svg
│ ├── undraw_Access_account_re_8spm.svg
│ ├── undraw_authentication_fsn5.svg
│ ├── undraw_enter_uhqk.svg
│ ├── undraw_my_password_d6kg.svg
│ └── undraw_secure_login_pdn4.svg
├── logo-192.png
├── logo-512.png
├── manifest.json
└── robot.txt
├── svelte.config.js
├── tailwind.config.cjs
└── tsconfig.json
/.env.example:
--------------------------------------------------------------------------------
1 | VITE_BASE_URL=http://localhost:3000
2 | VITE_KRATOS_BROWSER_URL=http://localhost:3000
3 | VITE_KRATOS_PUBLIC_URL=http://localhost:4433
4 | VITE_KRATOS_ADMIN_URL=http://localhost:4434
5 | VITE_SECURITY_MODE=cookie
6 |
7 | POSTGRES_USER=
8 | POSTGRES_PASSWORD=
9 | POSTGRES_DB=
10 |
11 | DSN=
12 |
13 | SERVE_PUBLIC_BASE_URL=${VITE_KRATOS_PUBLIC_URL}/
14 | SERVE_PUBLIC_CORS_ALLOWED_ORIGINS=${VITE_KRATOS_BROWSER_URL}
15 | SERVE_ADMIN_BASE_URL=${VITE_KRATOS_ADMIN_URL}/
16 | SELFSERVICE_FLOWS_ERROR_UI_URL=${VITE_KRATOS_BROWSER_URL}/error
17 | SELFSERVICE_FLOWS_SETTINGS_UI_URL=${VITE_KRATOS_BROWSER_URL}/settings
18 | SELFSERVICE_FLOWS_RECOVERY_UI_URL=${VITE_KRATOS_BROWSER_URL}/auth/recovery
19 | SELFSERVICE_FLOWS_VERIFICATION_UI_URL=${VITE_KRATOS_BROWSER_URL}/auth/verify
20 | SELFSERVICE_FLOWS_VERIFICATION_AFTER_DEFAULT_BROWSER_RETURN_URL=${VITE_KRATOS_BROWSER_URL}/
21 | SELFSERVICE_FLOWS_LOGOUT_AFTER_DEFAULT_BROWSER_RETURN_URL=${VITE_KRATOS_BROWSER_URL}/auth/login
22 | SELFSERVICE_FLOWS_LOGIN_UI_URL=${VITE_KRATOS_BROWSER_URL}/auth/login
23 | SELFSERVICE_FLOWS_REGISTRATION_UI_URL=${VITE_KRATOS_BROWSER_URL}/auth/registration
24 | SELFSERVICE_DEFAULT_BROWSER_RETURN_URL=${VITE_KRATOS_BROWSER_URL}/
25 | SELFSERVICE_WHITELISTED_RETURN_URLS=${VITE_KRATOS_BROWSER_URL}
26 |
27 | SECRETS_DEFAULT=
28 |
29 | LOG_LEVEL=debug
30 | LOG_FORMAT=text
31 | LOG_LEAK_SENSITIVE_VALUES=true
--------------------------------------------------------------------------------
/.eslintrc.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | parser: '@typescript-eslint/parser',
4 | extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended', 'prettier'],
5 | plugins: ['svelte3', '@typescript-eslint'],
6 | ignorePatterns: ['*.cjs'],
7 | overrides: [{ files: ['*.svelte'], processor: 'svelte3/svelte3' }],
8 | settings: {
9 | 'svelte3/typescript': () => require('typescript')
10 | },
11 | parserOptions: {
12 | sourceType: 'module',
13 | ecmaVersion: 2019
14 | },
15 | env: {
16 | browser: true,
17 | es2017: true,
18 | node: true
19 | }
20 | };
21 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # misc
2 | .DS_Store
3 | *.pem
4 | jwks.json
5 | *.jwks.json
6 |
7 | # dependencies
8 | /node_modules
9 | /.pnp
10 | .pnp.js
11 |
12 | # testing
13 | /coverage
14 |
15 | # sveltekit
16 | /.svelte-kit
17 | /package
18 |
19 | # production
20 | /build
21 |
22 | # serverless function
23 | /functions
24 |
25 | # debug
26 | npm-debug.log*
27 | yarn-debug.log*
28 | yarn-error.log*
29 | .pnpm-debug.log
30 |
31 | # local env files
32 | .env
33 | *.env
34 |
--------------------------------------------------------------------------------
/.kratos/identity.schema.json:
--------------------------------------------------------------------------------
1 | {
2 | "$id": "https://schemas.ory.sh/presets/kratos/quickstart/email-password/identity.schema.json",
3 | "$schema": "http://json-schema.org/draft-07/schema#",
4 | "title": "Person",
5 | "type": "object",
6 | "properties": {
7 | "traits": {
8 | "type": "object",
9 | "properties": {
10 | "email": {
11 | "type": "string",
12 | "format": "email",
13 | "title": "E-Mail",
14 | "minLength": 3,
15 | "ory.sh/kratos": {
16 | "credentials": {
17 | "password": {
18 | "identifier": true
19 | }
20 | },
21 | "verification": {
22 | "via": "email"
23 | },
24 | "recovery": {
25 | "via": "email"
26 | }
27 | }
28 | },
29 | "username": {
30 | "type": "string",
31 | "pattern": "^[a-z0-9._-]{6,15}$",
32 | "ory.sh/kratos": {
33 | "credentials": {
34 | "password": {
35 | "identifier": true
36 | }
37 | }
38 | }
39 | },
40 | "name": {
41 | "type": "object",
42 | "properties": {
43 | "first": {
44 | "title": "First Name",
45 | "type": "string"
46 | },
47 | "last": {
48 | "title": "Last Name",
49 | "type": "string"
50 | }
51 | }
52 | }
53 | },
54 | "required": ["email", "username"],
55 | "additionalProperties": false
56 | }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/.kratos/kratos.yml:
--------------------------------------------------------------------------------
1 | # see full reference in https://www.ory.sh/kratos/docs/reference/configuration/
2 | version: v0.6.3-alpha.1
3 |
4 | dsn: memory
5 |
6 | serve:
7 | public:
8 | base_url: http://127.0.0.1:4433/
9 | cors:
10 | allowed_origins:
11 | - http://127.0.0.1:3000
12 | allowed_methods:
13 | - POST
14 | - GET
15 | - PUT
16 | - PATCH
17 | - DELETE
18 | allowed_headers:
19 | - Authorization
20 | - Cookie
21 | exposed_headers:
22 | - Content-Type
23 | - Set-Cookie
24 | enabled: true
25 | debug: true
26 | admin:
27 | base_url: http://kratos:4434/
28 |
29 | selfservice:
30 | default_browser_return_url: http://127.0.0.1:3000/
31 | whitelisted_return_urls:
32 | - http://127.0.0.1:3000
33 | methods:
34 | password:
35 | enabled: true
36 | # oidc:
37 | # enabled: true
38 | # config:
39 | # providers:
40 | # - id: google
41 | # provider: google
42 | # client_id: INSERT GOOGLE ID HERE
43 | # client_secret: INSERT GOOGLE SECRET HERE
44 | # mapper_url: file:///etc/config/kratos/oidc/oidc.google.jsonnet
45 | # scope:
46 | # - profile
47 | # - email
48 | # - openid
49 | # requested_claims:
50 | # id_token:
51 | # email:
52 | # essential: true
53 | # email_verified:
54 | # essential: true
55 | # given_name:
56 | # essential: true
57 | # family_name: null
58 | # - id: facebook
59 | # provider: facebook
60 | # client_id: INSERT FACEBOOK ID HERE
61 | # client_secret: .INSERT FACEBOOK SECRET HERE
62 | # mapper_url: file:///etc/config/kratos/oidc/oidc.facebook.jsonnet
63 | # scope:
64 | # - email
65 |
66 | flows:
67 | error:
68 | ui_url: http://127.0.0.1:3000/error
69 |
70 | settings:
71 | ui_url: http://127.0.0.1:3000/settings
72 | privileged_session_max_age: 15m
73 |
74 | recovery:
75 | enabled: true
76 | ui_url: http://127.0.0.1:3000/auth/recovery
77 |
78 | verification:
79 | enabled: true
80 | ui_url: http://127.0.0.1:3000/auth/verify
81 | after:
82 | default_browser_return_url: http://127.0.0.1:3000/
83 |
84 | logout:
85 | after:
86 | default_browser_return_url: http://127.0.0.1:3000/auth/login
87 |
88 | login:
89 | ui_url: http://127.0.0.1:3000/auth/login
90 | lifespan: 10m
91 |
92 | registration:
93 | lifespan: 10m
94 | ui_url: http://127.0.0.1:3000/auth/registration
95 | after:
96 | password:
97 | hooks:
98 | - hook: session
99 | # oidc:
100 | # hooks:
101 | # - hook: session
102 |
103 | log:
104 | # Don't use these settings in prod
105 | level: debug
106 | format: text
107 | leak_sensitive_values: true
108 |
109 | # set in SECRETS_COOKIE and SECRETS_DEFAULT env variables
110 | secrets:
111 | default:
112 | - 54a955135cc1568c3b630436fab8c42d0ef397fda9ac90d07051187e2a574e72
113 | cookie:
114 | - a17d7608c7839f632d5f0a11dcc88b46fdc090defa454ee65e8090bd4dc54c6a
115 |
116 | # session:
117 | # lifespan: 720h
118 | # cookie:
119 | # domain: example.com
120 |
121 | hashers:
122 | argon2:
123 | parallelism: 1
124 | memory: 128MB
125 | iterations: 2
126 | salt_length: 16
127 | key_length: 16
128 |
129 | identity:
130 | default_schema_url: file:///etc/config/kratos/identity.schema.json
131 |
132 | courier:
133 | smtp:
134 | connection_uri: smtps://test:test@mailslurper:1025/?skip_ssl_verify=true
135 |
--------------------------------------------------------------------------------
/.kratos/oidc/oidc.facebook.jsonnet:
--------------------------------------------------------------------------------
1 | local claims = std.extVar('claims');
2 | {
3 | identity: {
4 | traits: {
5 | // Allowing unverified email addresses enables account
6 | // enumeration attacks, especially if the value is used for
7 | // e.g. verification or as a password login identifier.
8 | //
9 | // It is assumed that Facebook requires a email to be verifed before accessable via Oauth (because they don't provide an email_verified field).
10 | //
11 | // The email might be empty if the user is not allowed to an email scope.
12 | [if "email" in claims then "email" else null]: claims.email,
13 | },
14 | },
15 | }
--------------------------------------------------------------------------------
/.kratos/oidc/oidc.google.jsonnet:
--------------------------------------------------------------------------------
1 | local claims = {
2 | email_verified: false
3 | } + std.extVar('claims');
4 |
5 | {
6 | identity: {
7 | traits: {
8 | // Allowing unverified email addresses enables account
9 | // enumeration attacks, especially if the value is used for
10 | // e.g. verification or as a password login identifier.
11 | //
12 | // Therefore we only return the email if it (a) exists and (b) is marked verified
13 | [if "email" in claims && claims.email_verified then "email" else null]: claims.email,
14 | [if "given_name" in claims || "family_name" in claims then "name" else null]: {
15 | [if "given_name" in claims then "first" else null]: claims.given_name,
16 | [if "family_name" in claims then "last" else null]: claims.family_name,
17 | },
18 | },
19 | },
20 | }
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | engine-strict=true
2 |
--------------------------------------------------------------------------------
/.oathkeeper/access_rules.yml:
--------------------------------------------------------------------------------
1 | - id: 'ory:kratos:public'
2 | upstream:
3 | preserve_host: true
4 | url: 'http://kratos:4433'
5 | strip_path: /.ory/kratos/public
6 | match:
7 | url: 'http://localhost:3000/.ory/kratos/public/<**>'
8 | methods:
9 | - GET
10 | - POST
11 | - PUT
12 | - DELETE
13 | - PATCH
14 | authenticators:
15 | - handler: noop
16 | authorizer:
17 | handler: allow
18 | mutators:
19 | - handler: noop
20 |
21 | - id: 'ory:web:anonymous'
22 | upstream:
23 | preserve_host: true
24 | url: 'http://web:4435'
25 | match:
26 | url: 'http://localhost:3000/<{error,recovery,verify,auth/*,**.css,**.js,**.js.map,**.png,**.webp}{/,}>'
27 | methods:
28 | - GET
29 | authenticators:
30 | - handler: anonymous
31 | authorizer:
32 | handler: allow
33 | mutators:
34 | - handler: noop
35 |
36 | - id: 'ory:web:protected'
37 | upstream:
38 | preserve_host: true
39 | url: 'http://web:4435'
40 | match:
41 | url: 'http://localhost:3000/<{,debug,profile/*,settings}>'
42 | methods:
43 | - GET
44 | authenticators:
45 | - handler: cookie_session
46 | authorizer:
47 | handler: allow
48 | mutators:
49 | - handler: id_token
50 | errors:
51 | - handler: redirect
52 | config:
53 | to: http://localhost:3000/auth/login
54 |
--------------------------------------------------------------------------------
/.oathkeeper/oathkeeper.yml:
--------------------------------------------------------------------------------
1 | serve:
2 | proxy:
3 | port: 443
4 | tls:
5 | cert:
6 | path: /etc/certs/oathkeeper/svltkt.local+1.pem
7 | key:
8 | path: /etc/certs/oathkeeper/svltkt.local+1-key.pem
9 | cors:
10 | enabled: true
11 | allow_credentials: true
12 | allowed_origins:
13 | - http://localhost:3000
14 | allowed_methods:
15 | - POST
16 | - GET
17 | - PUT
18 | - PATCH
19 | - DELETE
20 | allowed_headers:
21 | - Authorization
22 | - Cookie
23 | exposed_headers:
24 | - Content-Type
25 | - Set-Cookie
26 | api:
27 | port: 4456
28 |
29 | errors:
30 | fallback:
31 | - json
32 |
33 | handlers:
34 | redirect:
35 | enabled: true
36 | config:
37 | to: http://localhost:3000/auth/login
38 |
39 | json:
40 | enabled: true
41 | config:
42 | verbose: true
43 |
44 | access_rules:
45 | repositories:
46 | - file:///etc/config/oathkeeper/access_rules.yml
47 |
48 | authenticators:
49 | anonymous:
50 | enabled: true
51 | config:
52 | subject: guest
53 |
54 | cookie_session:
55 | enabled: true
56 | config:
57 | check_session_url: http://kratos:4433/sessions/whoami
58 | preserve_path: true
59 | extra_from: '@this'
60 | subject_from: 'identity.id'
61 | only:
62 | - ory_kratos_session
63 |
64 | noop:
65 | enabled: true
66 |
67 | authorizers:
68 | allow:
69 | enabled: true
70 | deny:
71 | enabled: true
72 |
73 | mutators:
74 | header:
75 | enabled: true
76 | config:
77 | headers:
78 | X-User: '{{ print .Subject }}'
79 |
80 | noop:
81 | enabled: true
82 |
83 | id_token:
84 | enabled: true
85 | config:
86 | issuer_url: http://oathkeeper:4455/
87 | jwks_url: file:///etc/config/oathkeeper/jwks.json
88 | claims: |
89 | {
90 | "session": {{ .Extra | toJson }},
91 | "https://hasura.io/jwt/claims": {
92 | "x-hasura-allowed-roles": ["user", "admin"], "x-hasura-default-role": "user",
93 | "x-hasura-user-id": {{ print .Extra.identity.id | toJson }}
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | .svelte-kit/**
2 | static/**
3 | build/**
4 | node_modules/**
5 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "useTabs": true,
3 | "singleQuote": true,
4 | "trailingComma": "none",
5 | "printWidth": 100
6 | }
7 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | # Install dependencies only when needed
2 | FROM node:16-alpine AS deps
3 | # Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
4 | RUN apk add --no-cache libc6-compat
5 | # install dependencies
6 | WORKDIR /usr/src/app
7 | COPY package.json pnpm-lock.yaml ./
8 | RUN npx pnpm i --frozen-lockfile
9 |
10 | # Rebuild the source code only when needed
11 | FROM node:16-alpine AS builder
12 | WORKDIR /usr/src/app
13 | COPY . .
14 | COPY --from=deps /usr/src/app/node_modules ./node_modules
15 | RUN npx pnpm run build
16 |
17 | # Production image, copy all the files and run next
18 | FROM node:16-alpine AS runner
19 | WORKDIR /usr/src/app
20 |
21 | ENV NODE_ENV production
22 |
23 | RUN adduser -S ory -D -u 10000 -s /bin/nologin
24 |
25 | # COPY --from=builder /app/*.cjs ./
26 | # COPY --from=builder /app/*.config.js ./
27 | # COPY --from=builder /app/tsconfig.json ./
28 | # COPY --from=builder /app/static ./static
29 | # COPY --from=builder --chown=sveltekit:nodejs /app/.svelte-kit ./.svelte-kit
30 | COPY --from=builder --chown=sveltekit:nodejs /usr/src/app/build ./build
31 | COPY --from=builder /usr/src/app/node_modules ./node_modules
32 | COPY --from=builder /usr/src/app/package.json ./package.json
33 |
34 | USER 10000
35 |
36 | EXPOSE 3000
37 |
38 | CMD ["node", "./build"]
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # SvelteKit-Kratos
2 |
3 | > Basic SvelteKit example using [Ory Kratos](https://ory.sh/kratos) for authentication.
4 |
5 | ## Features
6 |
7 | - [x] Svelte via [SvelteKit](https://kit.svelte.dev)
8 | - [x] Authentication via [Kratos](https://ory.sh/kratos)
9 | - [x] Styling via [tailwindcss](https://tailwindcss.com)
10 | - [ ] GraphQL via [Hasura](https://hasura.io/)
11 |
12 | ## Quick start
13 |
14 | Once you've installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
15 |
16 | ```bash
17 | npm run dev
18 |
19 | # or start the server and open the app in a new browser tab
20 | npm run dev -- --open
21 |
22 | # to use https run
23 | npm run dev -- -H
24 | ```
25 |
26 | If running locally on port 80 and getting `listen EACCES: permission denied 0.0.0.0:80` try:
27 |
28 | ```bash
29 | sudo apt-get install libcap2-bin
30 | sudo setcap cap_net_bind_service=+ep `readlink -f \`which node\``
31 |
32 | # start sveltekit on port 80 or 443 if using https with -H flag
33 | npm run dev -- -p 80
34 | ```
35 |
36 | Start Kratos:
37 |
38 | ```bash
39 | docker compose up --build --force-recreate
40 | ```
41 |
42 | Create cookie/default secret
43 |
44 | ```bash
45 | openssl rand -base64 24
46 | ```
47 |
48 | Create cryptographic keys for Oathkeeper JWT:
49 |
50 | ```bash
51 | docker run oryd/oathkeeper:v0.38.11-beta.1 credentials generate --alg RS512 > ./.oathkeeper/jwks.json
52 | ```
53 |
54 | Create SSL certificates for local development
55 |
56 | ```bash
57 | mkdir certs && cd certs
58 | mkcert myapp.local "*.myapp.local" localhost 127.0.0.1
59 | ```
60 |
61 | Build a production version of your app by running:
62 |
63 | ```bash
64 | npm run build
65 | ```
66 |
67 | > You can preview the built app with `npm run preview`, regardless of whether you installed an adapter. This should _not_ be used to serve your app in production.
68 |
69 | ## TODO
70 |
71 | - [ ] Add [Oathkeeper](https://ory.sh/oathkeeper) as a reverse proxy
72 | - [ ] Fix misc styles issues for different browsers
73 | - [ ] Fix password toggle not working in Firefox (works in Chrome and Safari)
74 | - [ ] Add GraphQL via Hasura
75 |
76 | ## Disclaimer
77 |
78 | > I suggest using the Chrome browser to run this example.
79 |
80 | > SvelteKit is still in early beta and has various bugs, especially in Firefox
81 |
82 | > If you're on Windows and using WSL2 you might face a bug where the dev server constantly refreshes because of a websocket connection error when using host 127.0.0.1
83 |
--------------------------------------------------------------------------------
/docker-compose-oathkeeper.yml:
--------------------------------------------------------------------------------
1 | version: '3.9'
2 |
3 | services:
4 | kratos:
5 | environment:
6 | - SERVE_PUBLIC_BASE_URL=https://svltkt.dev/.ory/kratos/public/
7 |
8 | oathkeeper:
9 | image: oryd/oathkeeper:v0.38.11-beta.1
10 | depends_on:
11 | - kratos
12 | ports:
13 | - 80:80
14 | - 443:443
15 | - 4456:4456
16 | command: serve proxy -c "/etc/config/oathkeeper/oathkeeper.yml"
17 | environment:
18 | - LOG_LEVEL=debug
19 | restart: on-failure
20 | networks:
21 | - intranet
22 | volumes:
23 | - ./.oathkeeper:/etc/config/oathkeeper
24 | - ./certs:/etc/certs/oathkeeper
25 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '3.9'
2 |
3 | services:
4 | kratos-migrate:
5 | depends_on:
6 | - postgresd
7 | image: oryd/kratos:v0.6.3-alpha.1
8 | env_file:
9 | - ./.env
10 | volumes:
11 | - type: bind
12 | source: ./.kratos
13 | target: /etc/config/kratos
14 | command: -c /etc/config/kratos/kratos.yml migrate sql -e --yes
15 | restart: on-failure
16 | networks:
17 | - intranet
18 |
19 | kratos-cli:
20 | image: oryd/kratos:v0.6.3-alpha.1
21 | environment:
22 | - KRATOS_ADMIN_URL=http://kratos:4434
23 |
24 | kratos:
25 | depends_on:
26 | - kratos-migrate
27 | image: oryd/kratos:v0.6.3-alpha.1
28 | ports:
29 | - '4433:4433' # public
30 | - '4434:4434' # admin
31 | env_file:
32 | - ./.env
33 | volumes:
34 | - type: bind
35 | source: ./.kratos
36 | target: /etc/config/kratos
37 | command: serve -c /etc/config/kratos/kratos.yml --dev --watch-courier
38 | restart: unless-stopped
39 | networks:
40 | - intranet
41 |
42 | postgresd:
43 | image: postgres:13
44 | ports:
45 | - '5432:5432'
46 | env_file:
47 | - ./.env
48 | networks:
49 | - intranet
50 |
51 | mailslurper:
52 | image: oryd/mailslurper:latest-smtps
53 | ports:
54 | - 4436:4436
55 | - 4437:4437
56 | networks:
57 | - intranet
58 |
59 | networks:
60 | intranet:
61 |
--------------------------------------------------------------------------------
/nginx.conf:
--------------------------------------------------------------------------------
1 | server {
2 | listen 443 ssl;
3 | listen [::]:443 ssl http2;
4 |
5 | server_name svltkt.local;
6 | ssl_certificate /home/dre/workspace/svelte/sveltekit-test/certs/local/svltkt.local+1.pem;
7 | ssl_certificate_key /home/dre/workspace/svelte/sveltekit-test/certs/local/svltkt.local+1-key.pem;
8 | ssl_protocols TLSv1.2;
9 | ssl_ciphers "EECDH+ECDSA+AESGCM EECDH+aRSA+AESGCM EECDH+ECDSA+SHA384 EECDH+ECDSA+SHA256 EECDH+aRSA+SHA384 EECDH+aRSA+SHA256 EECDH+aRSA+RC4 EECDH EDH+aRSA HIGH !RC4 !aNULL !eNULL !LOW !3DES !MD5 !EXP !PSK !SRP !DSS";
10 |
11 | location / {
12 | proxy_pass http://localhost:3000/;
13 | proxy_set_header Host $http_host;
14 | proxy_set_header X-Real-IP $remote_addr;
15 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
16 | proxy_set_header X-Forwarded-Proto $scheme;
17 | # WebSocket support
18 | proxy_http_version 1.1;
19 | proxy_set_header Upgrade $http_upgrade;
20 | proxy_set_header Connection "upgrade";
21 | }
22 | }
23 |
24 | # Redirect http to https
25 | server {
26 | listen 80;
27 | listen [::]:80;
28 | server_name svltkt.local;
29 | return 301 https://$server_name$request_uri;
30 | }
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "kratos-sveltekit",
3 | "version": "0.0.1",
4 | "scripts": {
5 | "dev": "svelte-kit dev",
6 | "build": "svelte-kit build",
7 | "preview": "svelte-kit preview",
8 | "check": "svelte-check --tsconfig ./tsconfig.json",
9 | "check:watch": "svelte-check --tsconfig ./tsconfig.json --watch",
10 | "lint": "prettier --check --plugin-search-dir=. . && eslint --ignore-path .gitignore .",
11 | "format": "prettier --write --plugin-search-dir=. ."
12 | },
13 | "dependencies": {
14 | "@ory/kratos-client": "^0.6.3-alpha.1"
15 | },
16 | "devDependencies": {
17 | "@sveltejs/adapter-node": "next",
18 | "@sveltejs/kit": "next",
19 | "@typescript-eslint/eslint-plugin": "^4.28.0",
20 | "@typescript-eslint/parser": "^4.28.0",
21 | "autoprefixer": "^10.2.6",
22 | "cssnano": "^5.0.6",
23 | "eslint": "^7.29.0",
24 | "eslint-config-prettier": "^8.3.0",
25 | "eslint-plugin-svelte3": "^3.2.0",
26 | "postcss": "^8.3.5",
27 | "postcss-cli": "^8.3.1",
28 | "postcss-load-config": "^3.1.0",
29 | "postcss-nested": "^5.0.5",
30 | "prettier": "~2.2.1",
31 | "prettier-plugin-svelte": "^2.3.1",
32 | "svelte": "^3.38.2",
33 | "svelte-check": "^2.2.0",
34 | "svelte-preprocess": "^4.7.3",
35 | "tailwindcss": "^2.2.2",
36 | "tslib": "^2.3.0",
37 | "typescript": "^4.3.4"
38 | },
39 | "type": "module"
40 | }
41 |
--------------------------------------------------------------------------------
/postcss.config.cjs:
--------------------------------------------------------------------------------
1 |
2 | const tailwindcss = require('tailwindcss')
3 | const postcssNested = require('postcss-nested')
4 | const autoprefixer = require('autoprefixer')
5 | const cssnano = require('cssnano')
6 |
7 | const mode = process.env.NODE_ENV
8 | const dev = mode === 'development'
9 |
10 | module.exports = {
11 | plugins: [
12 | postcssNested,
13 | tailwindcss,
14 | autoprefixer,
15 | !dev &&
16 | cssnano({
17 | preset: 'default'
18 | })
19 | ]
20 | }
--------------------------------------------------------------------------------
/src/app.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | %svelte.head%
8 |
9 |
10 | %svelte.body%
11 |
12 |
13 |
--------------------------------------------------------------------------------
/src/global.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | @layer components {
6 | .field {
7 | @apply relative flex mt-4;
8 | }
9 | .form-label_settings {
10 | @apply flex items-center w-1/4 text-lg text-gray-500;
11 | }
12 | .input {
13 | @apply flex items-center w-full h-12 px-4 bg-gray-200 rounded focus:outline-none focus:ring-2;
14 | }
15 | }
--------------------------------------------------------------------------------
/src/global.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | interface ImportMetaEnv {
4 | [key: string]: T;
5 | }
6 |
--------------------------------------------------------------------------------
/src/hooks/index.ts:
--------------------------------------------------------------------------------
1 | import type { ServerRequest, ServerResponse } from '@sveltejs/kit/types/hooks';
2 | import type { MaybePromise } from '@sveltejs/kit/types/helper';
3 | import type { Session } from '@ory/kratos-client';
4 | import { kratosPublicApi } from '$lib/services/kratos';
5 | import config from '$lib/config';
6 |
7 | interface Locals {
8 | session: Session;
9 | }
10 |
11 | export const handle = async ({
12 | request,
13 | resolve
14 | }: {
15 | request: ServerRequest;
16 | resolve: (request: ServerRequest) => MaybePromise;
17 | }) => {
18 | try {
19 | const { status, data } = await kratosPublicApi.toSession(undefined, {
20 | headers: {
21 | Authorization: `${request.headers.authorization}`,
22 | Cookie: `${request.headers.cookie}`,
23 | Origin: config.baseUrl
24 | },
25 | credentials: 'include'
26 | });
27 |
28 | if (status === 401) {
29 | request.locals.session = undefined;
30 | return await resolve(request);
31 | }
32 |
33 | request.locals.session = data;
34 |
35 | const response = await resolve(request);
36 |
37 | return {
38 | ...response,
39 | headers: {
40 | ...response.headers
41 | }
42 | };
43 | } catch (error) {
44 | // console.log('hooks error', error.response.data.error);
45 | if (error.response.data.error.code === 401) {
46 | return await resolve(request);
47 | }
48 | }
49 | };
50 |
51 | export const getSession = (request: ServerRequest) => {
52 | return {
53 | user: request.locals.session && {
54 | // only include properties needed client-side —
55 | // exclude anything else attached to the user
56 | // like access tokens etc
57 | id: request.locals.session.identity.id,
58 | username: request.locals.session.identity.traits.username,
59 | email: request.locals.session?.identity?.traits.email,
60 | first_name: request.locals.session?.identity?.traits.name.first,
61 | last_name: request.locals.session?.identity?.traits.name.last
62 | }
63 | };
64 | };
65 |
--------------------------------------------------------------------------------
/src/lib/Confirm.svelte:
--------------------------------------------------------------------------------
1 |
52 |
53 | {#if open}
54 | {
61 | if (open) {
62 | if (key === 'Escape') {
63 | open = false;
64 | } else if (shouldSubmitOnEnter && key === 'Enter') {
65 | dispatch('submit');
66 | }
67 | }
68 | }}
69 | on:click
70 | on:click={() => {
71 | if (!didClickInnerModal && !preventCloseOnClickOutside) open = false;
72 | didClickInnerModal = false;
73 | }}
74 | on:mouseover
75 | on:mouseenter
76 | on:mouseleave
77 | on:transitionend={(e) => {
78 | if (e.propertyName === 'transform') {
79 | dispatch('transitionend', { open });
80 | }
81 | }}
82 | in:fade={{ duration: 200 }}
83 | out:fade={{ delay: 200, duration: 200 }}
84 | {...$$restProps}
85 | >
86 |
{
94 | didClickInnerModal = true;
95 | }}
96 | in:fly={{
97 | y: -10,
98 | delay: 200,
99 | duration: 200
100 | }}
101 | out:fly={{
102 | y: -10,
103 | duration: 200
104 | }}
105 | >
106 |
133 |
134 |
135 |
136 |
155 |
156 |
157 | {/if}
158 |
--------------------------------------------------------------------------------
/src/lib/InlineSvg.svelte:
--------------------------------------------------------------------------------
1 |
67 |
--------------------------------------------------------------------------------
/src/lib/Kratos/AuthContainer.svelte:
--------------------------------------------------------------------------------
1 |
9 |
10 |
15 |
16 |
17 |
18 |
21 | {config.projectName}
22 |
23 |
24 |
25 |
26 |
29 |
30 |
31 | {#if flowType === 'registration'}
32 |
33 | Already have an account? Log in.
36 |
37 | {/if}
38 |
39 | {#if flowType === 'login'}
40 |
47 | {/if}
48 |
49 |
50 |
51 |
52 |
53 |
54 |

55 |
56 |
57 |
--------------------------------------------------------------------------------
/src/lib/Kratos/AuthForm.svelte:
--------------------------------------------------------------------------------
1 |
5 |
6 |
15 |
--------------------------------------------------------------------------------
/src/lib/Kratos/FormSectionHeader.svelte:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
9 |
10 | {text}
11 |
12 |
13 |
--------------------------------------------------------------------------------
/src/lib/Kratos/InputDefault.svelte:
--------------------------------------------------------------------------------
1 |
13 |
14 |
15 |
16 |
21 |
22 |
31 |
32 |
33 |
34 |
35 |
45 |
--------------------------------------------------------------------------------
/src/lib/Kratos/InputHidden.svelte:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/src/lib/Kratos/InputPassword.svelte:
--------------------------------------------------------------------------------
1 |
18 |
19 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/src/lib/Kratos/Message.svelte:
--------------------------------------------------------------------------------
1 |
5 |
6 | {#if messages}
7 | {#each messages as { type, text }}
8 |
15 | {text}
16 |
17 | {/each}
18 | {/if}
19 |
--------------------------------------------------------------------------------
/src/lib/Kratos/PasswordToggle.svelte:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 |
22 |
23 |
--------------------------------------------------------------------------------
/src/lib/Kratos/SubmitButton.svelte:
--------------------------------------------------------------------------------
1 |
7 |
8 |
17 |
--------------------------------------------------------------------------------
/src/lib/Link.svelte:
--------------------------------------------------------------------------------
1 |
8 |
9 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/src/lib/Nav.svelte:
--------------------------------------------------------------------------------
1 |
11 |
12 |
13 |
16 |
17 |
18 |
21 | {config.projectName}
22 |
23 |
24 |
83 |
84 |
85 |
86 |
--------------------------------------------------------------------------------
/src/lib/config.ts:
--------------------------------------------------------------------------------
1 | type SecurityMode = 'cookie' | 'jwt';
2 |
3 | const baseUrl = import.meta.env.VITE_BASE_URL || '/';
4 |
5 | let securityMode: SecurityMode = import.meta.env.VITE_SECURITY_MODE || 'cookie';
6 | let browserUrl = import.meta.env.VITE_KRATOS_BROWSER_URL || 'http://localhost:3000';
7 | let publicUrl = import.meta.env.VITE_KRATOS_PUBLIC_URL;
8 | let adminUrl = import.meta.env.VITE_KRATOS_ADMIN_URL;
9 | let jwksUrl = import.meta.env.VITE_JWKS_URL || '/';
10 | let projectName = 'Sveltekit Kratos';
11 |
12 | export default {
13 | kratos: {
14 | browser: browserUrl,
15 | admin: adminUrl,
16 | public: publicUrl
17 | },
18 | baseUrl,
19 | jwksUrl,
20 | projectName,
21 | securityMode
22 | };
23 |
--------------------------------------------------------------------------------
/src/lib/helpers.ts:
--------------------------------------------------------------------------------
1 | import type { UiNode, UiNodeInputAttributes } from '@ory/kratos-client';
2 | import type { UiNodeAnchorAttributes, UiNodeTextAttributes } from '@ory/kratos-client/api';
3 | import config from '$lib/config';
4 |
5 | const ui: { [key: string]: { title: string; position: number } } = {
6 | // You could add custom translations here if you want to:
7 | //
8 | // 'traits.email': {
9 | // title: 'E-Mail',
10 | // }
11 | };
12 |
13 | type Translations = typeof ui;
14 |
15 | export const getUiNodes = (nodes: UiNode[]) =>
16 | nodes.map((node) => {
17 | let attributes = node.attributes as UiNodeInputAttributes;
18 | const { value, ...rest } = attributes;
19 | let value_ = value as string;
20 | return { ...node, attributes: { value: value_, ...rest } };
21 | });
22 |
23 | export const getTitle = (n: UiNode): string => {
24 | switch (n.type) {
25 | case 'a':
26 | return (n.attributes as UiNodeAnchorAttributes).title.text;
27 | case 'img':
28 | return n.meta.label?.text || '';
29 | case 'input':
30 | const key = (n.attributes as UiNodeInputAttributes).name;
31 | if (n.meta?.label?.text) {
32 | return n.meta.label.text;
33 | } else if (key in ui) {
34 | return ui[key as keyof Translations].title;
35 | }
36 | return key;
37 | case 'text':
38 | return (n.attributes as UiNodeTextAttributes).text.text;
39 | }
40 |
41 | return '';
42 | };
43 |
44 | export const getAttribute = (node: UiNode) => node.attributes as UiNodeInputAttributes;
45 |
46 | export const isString = (x: any): x is string => typeof x === 'string';
47 |
48 | export const logoutUrl = `${config.kratos.public}/self-service/browser/flows/logout`;
49 |
--------------------------------------------------------------------------------
/src/lib/services/kratos.ts:
--------------------------------------------------------------------------------
1 | import { Configuration, PublicApi, AdminApi } from '@ory/kratos-client';
2 | import config from '$lib/config';
3 |
4 | export const kratosPublicApi = new PublicApi(
5 | new Configuration({
6 | basePath: config.kratos.public
7 | })
8 | );
9 |
10 | export const kratosAdminApi = new AdminApi(
11 | new Configuration({
12 | basePath: config.kratos.admin
13 | })
14 | );
15 |
--------------------------------------------------------------------------------
/src/lib/types.ts:
--------------------------------------------------------------------------------
1 | import type {
2 | LoginFlow,
3 | RecoveryFlow,
4 | RegistrationFlow,
5 | SettingsFlow,
6 | VerificationFlow
7 | } from '@ory/kratos-client';
8 |
9 | export type KratosFlowType =
10 | | 'registration'
11 | | 'login'
12 | | 'settings'
13 | | 'verification'
14 | | 'recovery'
15 | | 'error';
16 |
17 | export type AuthFlowType =
18 | | LoginFlow
19 | | RegistrationFlow
20 | | VerificationFlow
21 | | RecoveryFlow
22 | | SettingsFlow;
23 |
24 | export interface UserSession {
25 | user: {
26 | id: string;
27 | email: string;
28 | username: string;
29 | first_name: string;
30 | last_name: string;
31 | };
32 | }
33 |
--------------------------------------------------------------------------------
/src/routes/__error.svelte:
--------------------------------------------------------------------------------
1 |
9 |
10 |
17 |
18 | {#if status === 404}
19 |
20 |
23 |
24 |
27 | Sorry, this page isn't available
28 |
29 |
34 |
35 |
36 |

37 |
38 |
39 |
40 | {:else}
41 | {title}
42 | {/if}
43 |
44 |
47 |
--------------------------------------------------------------------------------
/src/routes/__layout.svelte:
--------------------------------------------------------------------------------
1 |
11 |
12 |
13 |
14 |
15 | Kratos SvelteKit
16 |
17 |
18 |
19 | {#if !$page.path.includes('auth')}
20 |
21 | {/if}
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/src/routes/_load.ts:
--------------------------------------------------------------------------------
1 | import type { Load } from '@sveltejs/kit';
2 | import type { UserSession } from '$lib/types';
3 |
4 | export const createLoad = (path?: string) => {
5 | const load: Load = async ({ session }: { session: UserSession }) => {
6 | if (session.user) {
7 | return {
8 | props: {
9 | session
10 | }
11 | };
12 | }
13 |
14 | return {
15 | status: 302,
16 | redirect: path || '/auth/login'
17 | };
18 | };
19 |
20 | return load;
21 | };
22 |
--------------------------------------------------------------------------------
/src/routes/about.svelte:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 | About - Kratos SvelteKit
7 |
8 |
9 |
10 |
About
11 |
12 |
--------------------------------------------------------------------------------
/src/routes/api/auth/[...auth].ts:
--------------------------------------------------------------------------------
1 | import type { Request } from '@sveltejs/kit';
2 | import type { ErrorResponse } from '@ory/kratos-client';
3 | import type { AuthFlowType } from '$lib/types';
4 | import { kratosAdminApi } from '$lib/services/kratos';
5 |
6 | export const get = async (req: Request) => {
7 | const flowId = req.headers.flow_id;
8 | const error = req.headers.error;
9 | const flowType = req.params.auth;
10 |
11 | let authFlow =
12 | flowType === 'registration'
13 | ? 'getSelfServiceRegistrationFlow'
14 | : flowType === 'recovery'
15 | ? 'getSelfServiceRecoveryFlow'
16 | : flowType === 'verification'
17 | ? 'getSelfServiceVerificationFlow'
18 | : flowType == 'settings'
19 | ? 'getSelfServiceSettingsFlow'
20 | : flowType == 'error'
21 | ? 'getSelfServiceError'
22 | : 'getSelfServiceLoginFlow';
23 |
24 | try {
25 | const {
26 | status,
27 | data
28 | }: { status: number; data: AuthFlowType | ErrorResponse } = await kratosAdminApi[authFlow](
29 | flowType === 'error' ? error : flowId
30 | );
31 |
32 | return {
33 | body: { data },
34 | status,
35 | headers: {
36 | 'Content-Type': 'application/json'
37 | }
38 | };
39 | } catch {}
40 | };
41 |
--------------------------------------------------------------------------------
/src/routes/api/auth/delete.ts:
--------------------------------------------------------------------------------
1 | import type { Request } from '@sveltejs/kit';
2 | import { Configuration, AdminApi } from '@ory/kratos-client';
3 | import config from '$lib/config';
4 |
5 | const configuration = new Configuration({
6 | basePath: config.kratos.admin
7 | });
8 |
9 | const kratos = new AdminApi(configuration);
10 |
11 | export const del = async (request: Request) => {
12 | const { id } = JSON.parse(request.body as string);
13 |
14 | const { data } = await kratos.getIdentity(id);
15 |
16 | if (id === data.id) {
17 | await kratos.deleteIdentity(id);
18 | return {
19 | status: 200,
20 | body: { success: true, id }
21 | };
22 | }
23 |
24 | return {
25 | status: 404,
26 | body: {
27 | error: 'User not found'
28 | }
29 | };
30 | };
31 |
--------------------------------------------------------------------------------
/src/routes/auth/_load.ts:
--------------------------------------------------------------------------------
1 | import type { Load } from '@sveltejs/kit';
2 | import config from '$lib/config';
3 | import { isString } from '$lib/helpers';
4 | import type { AuthFlowType, KratosFlowType } from '$lib/types';
5 |
6 | export const createLoad = (flowType: KratosFlowType) => {
7 | const load: Load = async ({ page, fetch }) => {
8 | const flowID = page.query.get('flow');
9 |
10 | const res = await fetch(`/api/auth/${flowType}`, {
11 | headers: {
12 | flow_id: flowID
13 | }
14 | });
15 |
16 | if (!res.ok) {
17 | return {
18 | status: 302,
19 | redirect: `${config.kratos.public}/self-service/${flowType}/browser`
20 | };
21 | }
22 |
23 | const { data: flow }: { status: number; data: AuthFlowType } = await res.json();
24 |
25 | return {
26 | props: {
27 | ui: flow.ui
28 | }
29 | };
30 | };
31 |
32 | return load;
33 | };
34 |
--------------------------------------------------------------------------------
/src/routes/auth/login.svelte:
--------------------------------------------------------------------------------
1 |
6 |
7 |
22 |
23 |
24 |
25 |
26 | {#each nodes as { messages, meta: { label }, attributes: { name, type, value, disabled } }}
27 | {#if type === 'hidden'}
28 |
29 | {/if}
30 | {#if type === 'text'}
31 |
39 | {/if}
40 | {#if name === 'password'}
41 |
49 | {/if}
50 | {#if type === 'submit'}
51 |
52 | {label?.text}
53 |
54 | {/if}
55 | {/each}
56 |
57 |
58 |
--------------------------------------------------------------------------------
/src/routes/auth/recovery.svelte:
--------------------------------------------------------------------------------
1 |
6 |
7 |
21 |
22 |
23 |
24 |
Forgot Your Password?
25 |
26 | We get it, stuff happens. Just enter your email address below and we'll send you a link to
27 | reset your password!
28 |
29 |
30 |
31 |
32 | {#each nodes as { messages, meta: { label }, attributes: { name, type, value, disabled } }}
33 | {#if type === 'hidden'}
34 |
35 | {/if}
36 | {#if type === 'email'}
37 |
45 | {/if}
46 | {#if name === 'password'}
47 |
55 | {/if}
56 | {#if type === 'submit'}
57 |
58 | {label?.text}
59 |
60 | {/if}
61 | {/each}
62 |
63 |
64 |
--------------------------------------------------------------------------------
/src/routes/auth/registration.svelte:
--------------------------------------------------------------------------------
1 |
6 |
7 |
47 |
48 |
49 |
50 |
51 | {#each sortedNodes as { messages, meta: { label }, attributes: { name, type, value, disabled } }}
52 | {#if type === 'hidden'}
53 |
54 | {/if}
55 | {#if type === 'email' || type === 'text'}
56 |
64 | {/if}
65 | {#if name === 'password'}
66 | validatePassword(e)}
75 | />
76 |
77 |
78 | Your password must be at least 6 characters long.
79 | {passwordValue?.length > 5 ? '✓' : ''}
82 |
83 | {#if focused && passwordValue?.length > 0}
84 | {#if strength < 1}
85 |
Weak
86 | {/if}
87 | {#if strength > 0 && strength < 2}
88 |
Medium
89 | {/if}
90 | {#if strength > 1 && strength < 3}
91 |
Really Strong
92 | {/if}
93 | {#if strength > 2}
94 |
Super secure
95 | {/if}
96 | {/if}
97 |
98 | {/if}
99 | {#if type === 'submit'}
100 |
101 | {label?.text}
102 |
103 | {/if}
104 | {/each}
105 |
106 |
107 |
--------------------------------------------------------------------------------
/src/routes/auth/verify.svelte:
--------------------------------------------------------------------------------
1 |
6 |
7 |
21 |
22 |
23 |
24 |
Verify your email address
25 |
26 |
27 |
28 | {#each nodes as { messages, meta: { label }, attributes: { name, type, value, disabled } }}
29 | {#if type === 'hidden'}
30 |
31 | {/if}
32 | {#if type === 'email'}
33 |
41 | {/if}
42 | {#if name === 'password'}
43 |
51 | {/if}
52 | {#if type === 'submit'}
53 |
54 | {label?.text}
55 |
56 | {/if}
57 | {/each}
58 |
59 |
60 |
--------------------------------------------------------------------------------
/src/routes/error.svelte:
--------------------------------------------------------------------------------
1 |
40 |
41 |
44 |
45 |
46 | Error - Kratos SvelteKit
47 |
48 |
49 |
50 |
An error occurred
51 |
{message}
52 |
53 |
--------------------------------------------------------------------------------
/src/routes/index.svelte:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 | {#if $session.user}
7 |
Hello, @{$session.user.username}
8 | {/if}
9 |
10 |
Welcome to SvelteKit with Authentication via Ory Kratos!
11 |
12 | Visit kit.svelte.dev to read
13 | the sveltekit documentation
14 |
15 |
16 | Visit www.ory.sh/kratos/docs to read the Ory Kratos documentation
19 |
20 |
21 |
--------------------------------------------------------------------------------
/src/routes/profile/@[user]/index.svelte:
--------------------------------------------------------------------------------
1 |
6 |
7 |
13 |
14 | Welcom back, {session.user.username}
15 |
16 | Your info:
17 |
18 | {JSON.stringify(session.user, null, 2)}
19 |
20 | Logout
24 |
--------------------------------------------------------------------------------
/src/routes/profile/[user].svelte:
--------------------------------------------------------------------------------
1 |
11 |
--------------------------------------------------------------------------------
/src/routes/settings.svelte:
--------------------------------------------------------------------------------
1 |
40 |
41 |
75 |
76 |
77 |
78 |
79 |
80 | {#each nodes as { messages, meta: { label }, attributes: { name, type, value, disabled } }}
81 | {#if type === 'hidden'}
82 |
83 | {/if}
84 | {#if type === 'email' || name === 'traits.username'}
85 |
94 |
99 |
100 | {/if}
101 |
102 | {#if name === 'traits.name.first' || name === 'traits.name.last'}
103 |
112 |
117 |
118 | {/if}
119 | {#if type === 'password'}
120 |
121 |
129 | {/if}
130 | {#if type === 'submit'}
131 |
132 | {label?.text}
133 |
134 | {/if}
135 | {/each}
136 |
137 |
142 |
(open = false)}
149 | on:open
150 | on:close
151 | on:submit={() => deleteUser(session.user.id)}
152 | >
153 | This is a permanent action and cannot be undone.
154 |
155 |
156 |
--------------------------------------------------------------------------------
/static/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/drejohnson/sveltekit-kratos/32b754c281e272706953dc2f1239840f6b7c8d8e/static/favicon.png
--------------------------------------------------------------------------------
/static/images/404.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/static/images/undraw_Access_account_re_8spm.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/static/images/undraw_authentication_fsn5.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/static/images/undraw_enter_uhqk.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/static/images/undraw_my_password_d6kg.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/static/images/undraw_secure_login_pdn4.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/static/logo-192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/drejohnson/sveltekit-kratos/32b754c281e272706953dc2f1239840f6b7c8d8e/static/logo-192.png
--------------------------------------------------------------------------------
/static/logo-512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/drejohnson/sveltekit-kratos/32b754c281e272706953dc2f1239840f6b7c8d8e/static/logo-512.png
--------------------------------------------------------------------------------
/static/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "background_color": "#ffffff",
3 | "theme_color": "#5cb85c",
4 | "name": "Sveltekit Kratos",
5 | "short_name": "Sveltekit Kratos",
6 | "display": "minimal-ui",
7 | "start_url": "/",
8 | "icons": [
9 | {
10 | "src": "logo-192.png",
11 | "sizes": "192x192",
12 | "type": "image/png"
13 | },
14 | {
15 | "src": "logo-512.png",
16 | "sizes": "512x512",
17 | "type": "image/png"
18 | }
19 | ]
20 | }
--------------------------------------------------------------------------------
/static/robot.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
--------------------------------------------------------------------------------
/svelte.config.js:
--------------------------------------------------------------------------------
1 | import { readFileSync } from 'fs';
2 | import preprocess from 'svelte-preprocess';
3 | import node from '@sveltejs/adapter-node';
4 |
5 | /** @type {import('@sveltejs/kit').Config} */
6 | const config = {
7 | // Consult https://github.com/sveltejs/svelte-preprocess
8 | // for more information about preprocessors
9 | preprocess: preprocess({
10 | defaults: {
11 | style: 'postcss'
12 | },
13 | postcss: true
14 | }),
15 |
16 | kit: {
17 | adapter: node(),
18 | // hydrate the element in src/app.html
19 | target: '#svelte',
20 | hostHeader: 'X-Forwarded-Host',
21 | // trailingSlash: 'ignore',
22 | vite: () => ({
23 | server: {
24 | host: '0.0.0.0',
25 | port: 3000,
26 | https: {
27 | key: readFileSync('./certs/local/svltkt.local+1-key.pem'),
28 | cert: readFileSync('./certs/local/svltkt.local+1.pem')
29 | },
30 | hmr: {
31 | host: 'localhost',
32 | protocol: 'wss',
33 | port: 24678
34 | }
35 | }
36 | })
37 | }
38 | };
39 |
40 | export default config;
41 |
--------------------------------------------------------------------------------
/tailwind.config.cjs:
--------------------------------------------------------------------------------
1 | const colors = require('tailwindcss/colors');
2 |
3 | module.exports = {
4 | mode: 'jit',
5 | purge: ['./src/**/*.{html,js,svelte,ts}'],
6 | darkMode: 'class',
7 | theme: {
8 | fontFamily: {
9 | display: ['Bebas Neue', 'cursive'],
10 | sans: ['Poppins', 'sans-serif'],
11 | body: ['Poppins', 'sans-serif']
12 | },
13 | colors: {
14 | transparent: 'transparent',
15 | current: 'currentColor',
16 | black: colors.black,
17 | white: colors.white,
18 | gray: colors.trueGray,
19 | red: colors.red,
20 | yellow: colors.amber,
21 | blue: colors.blue,
22 | indigo: colors.indigo
23 | },
24 | extend: {
25 | width: {
26 | '100px': '100px',
27 | '150px': '150px',
28 | '200px': '200px',
29 | '300px': '300px',
30 | '400px': '400px',
31 | '500px': '500px'
32 | },
33 | height: {
34 | 'screen-90': '90vh',
35 | 'screen-80': '80vh',
36 | '100px': '100px',
37 | '200px': '200px',
38 | '300px': '300px',
39 | '400px': '400px',
40 | '500px': '500px'
41 | },
42 | zIndex: {
43 | negative: '-1'
44 | }
45 | }
46 | },
47 | variants: {
48 | extend: {}
49 | },
50 | plugins: []
51 | };
52 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "moduleResolution": "node",
4 | "module": "es2020",
5 | "lib": ["es2020"],
6 | "target": "es2019",
7 | /**
8 | svelte-preprocess cannot figure out whether you have a value or a type, so tell TypeScript
9 | to enforce using \`import type\` instead of \`import\` for Types.
10 | */
11 | "importsNotUsedAsValues": "error",
12 | "isolatedModules": true,
13 | "resolveJsonModule": true,
14 | /**
15 | To have warnings/errors of the Svelte compiler at the correct position,
16 | enable source maps by default.
17 | */
18 | "sourceMap": true,
19 | "esModuleInterop": true,
20 | "skipLibCheck": true,
21 | "forceConsistentCasingInFileNames": true,
22 | "baseUrl": ".",
23 | "allowJs": true,
24 | "checkJs": true,
25 | "paths": {
26 | "$lib/*": ["src/lib/*"]
27 | }
28 | },
29 | "include": ["src/**/*.d.ts", "src/**/*.js", "src/**/*.ts", "src/**/*.svelte"]
30 | }
31 |
--------------------------------------------------------------------------------