├── .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 | 157 | {/if} 158 | -------------------------------------------------------------------------------- /src/lib/InlineSvg.svelte: -------------------------------------------------------------------------------- 1 | 2 | 3 | menu 4 | 5 | 6 | 7 | menu-dots 8 | 11 | 12 | 13 | cross 14 | 17 | 18 | 19 | 22 | 23 | 24 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 38 | 39 | 40 | 43 | 44 | 45 | 46 | 47 | 48 | 51 | 52 | 53 | 56 | 57 | 58 | 61 | 62 | 65 | 66 | 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 |
41 | Register new account 42 | / 43 | Forgot password? 46 |
47 | {/if} 48 |
49 |
50 |
51 | 52 | 53 |
54 | 55 |
56 |
57 | -------------------------------------------------------------------------------- /src/lib/Kratos/AuthForm.svelte: -------------------------------------------------------------------------------- 1 | 5 | 6 |
13 | 14 | 15 | -------------------------------------------------------------------------------- /src/lib/Kratos/FormSectionHeader.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 |
6 |
7 |
8 |
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 |
20 |
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 | 8 | {#if showPassword} 9 | 15 | {:else} 16 | 20 | {/if} 21 | 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 | Page not found 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 |
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 | warning -------------------------------------------------------------------------------- /static/images/undraw_Access_account_re_8spm.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /static/images/undraw_authentication_fsn5.svg: -------------------------------------------------------------------------------- 1 | authentication -------------------------------------------------------------------------------- /static/images/undraw_enter_uhqk.svg: -------------------------------------------------------------------------------- 1 | enter -------------------------------------------------------------------------------- /static/images/undraw_my_password_d6kg.svg: -------------------------------------------------------------------------------- 1 | my password -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------