Giter VIP home page Giter VIP logo

teamhanko / hanko Goto Github PK

View Code? Open in Web Editor NEW
5.4K 29.0 790.0 37.27 MB

Auth and User Management for the passkey era. An open source alternative to Auth0 and Clerk.

Home Page: https://hanko.io

License: Other

Dockerfile 0.36% Go 51.79% JavaScript 0.64% Shell 0.03% TypeScript 40.22% HTML 1.83% Sass 1.22% CSS 2.43% Svelte 0.75% Vue 0.73%
passkeys webauthn fido2 authentication passwordless jwt oauth sign-in-with-apple sign-in-with-google sso

hanko's Introduction


Test Status Build Status Go Report Card GoDoc npm (scoped) npm (scoped)

About Hanko

Hanko is an open-source authentication and user management solution with a focus on moving the login beyond passwords while being 100% deployable today.

  • Built for passkeys as introduced by Apple, Google, and Microsoft
  • Fast integration with Hanko Elements web components (login box and user profile)
  • API-first, small footprint, cloud-native

Available for self-hosting and on Hanko Cloud.

Hanko is built and maintained by Hanko.io, an active member of the FIDO Alliance.

We take you on the journey beyond passwords ...

... and make sure your users won't get lost on the way. Passwordless logins have been promised to us for quite some time. But until now, "passwordless" was mostly a compromise that only worked for some of the users and had some severe drawbacks that ultimately led to passwords still being present at almost every login. It's only very recently that passkeys were announced, and the ecosystem of devices, browsers, and operating systems is finally ready to truly move beyond passwords.

With most devices and browsers now shipping with passkey support and convenient built-in authentication technology like Touch ID, Face ID, and Windows Hello, a much better login experience is enabled that will replace passwords for good. Hanko is built for that shift.

Build your passkey-powered auth stack with a few lines of code – and never look back.

Architecture

The main building blocks of the Hanko project are

  • backend - An authentication API for passkeys, passcodes, and (optional) passwords, OAuth SSO, as well as user management and JWT issuing
  • hanko-elements - Web components made for Hanko backend that provide onboarding and login functionality and are customizable with CSS
  • hanko-frontend-sdk - A client package for using the Hanko API

The remainder of the repository consists of:

Getting started

  1. Try our hosted live example and our companion page passkeys.io or use the quickstart app to get a feel for the user experience provided by an application that leverages the Hanko backend API and our custom web component
  2. To run the project locally, there are two options available:
    • Bare metal:
      • Head over to the backend section to learn how to get it up and running for your own project. Use Hanko Cloud for a hosted backend.
    • Docker:
      • If you prefer to use Docker to run the project locally, please visit the Run the quickstart for information on how to run the project. This will create everything, including frontend and backend components.
        • If you wish to keep only the backend components, you can modify the quickstart.yaml to remove the unnecessary services. To make changes to the configuration to meet your needs, modify config.yaml.
  3. Then, integrate hanko-elements – we provide example applications and guides for your favourite frontend framework in the official documentation
  4. if you have an enterprise license or use Hanko Cloud you can also integrate SAML SSO. Feel free to use this guide to start with SAML SSO

If you want to use the Hanko backend API but prefer to build your own UI, you can still make use of the hanko-frontend-sdk. It forms the basis of our web components, and the client it provides handles communication with the Hanko backend API and saves you the time of rolling your own.

Contact us

Schedule a Hanko demo. Learn how Hanko will speed up your registration and login flows with passkeys.

Book us with Cal.com

Roadmap

Watch our releases, leave a star, join our Discord community, or sign up to our product news to follow the development. Here's a brief overview of our current roadmap:

Status Feature
Passkeys
Email passcodes
Passwords
JWT signing
User management API
📢 Hanko Alpha Release
<hanko-auth> web component
Customizable CSS
📢 Hanko Beta Release
JavaScript frontend SDK
Passkey autofill (Conditional UI)
Audit logs API
Security Key support
Mobile app support
<hanko-profile> web component
Rate limiting
OAuth logins (Sign in with Apple/Google/GitHub)
i18n & custom translations
User import
Disable sign-ups
User export
SAML Enterprise SSO
Webhooks
⚙️ API-supported auth flows
⚙️ Passkey-only and OAuth-only configurations
⚙️ Username support (non-email)
⚙️ Optional / user-deleteable passwords
⚙️ OIDC Enterprise SSO
⚙️ 2FA (TOTP, security keys)
Email templates & i18n
Refresh tokens / sessions
📢 Hanko 1.0 Release
<hanko-menu> web component
Custom Social SSO connections (OIDC/OAuth2)
Email security notifications
Custom JWT claims
Mobile SDKs
SMS passcodes

Additional features that have been requested or that we would like to build but are currently not on the roadmap:

  • Custom user data / fields
  • Privileged sessions & step-up authentication
  • Bot protection / CAPTCHA
  • Hosted auth pages / OIDC provider

Community

Questions, bugs, ideas

If you have any questions or issues, please check this project's Q&A section in discussions and the open issues. Feel free to comment on existing issues or create a new issue if you encounter any bugs or have a feature request. For yet unanswered questions, feedback, or new ideas, please open a new discussion.

Discord community & X

We invite you to join our growing Discord community if you want to get the latest updates on passkeys, WebAuthn, and this project or if you just want to chat with us. You can also follow us on X.

Licenses

hanko-elements and hanko-frontend-sdk are licensed under the MIT License. Everything else in this repository, including hanko backend, is licensed under the AGPL-3.0.

hanko's People

Contributors

alienishi avatar amjed-ali-k avatar ashutosh-bhadauriya avatar bjoern-m avatar chigala avatar dependabot[bot] avatar esther-lita avatar flxmgdnz avatar freddydevelop avatar heysagnik avatar hilli avatar ignisda avatar irby avatar lfleischmann avatar like-a-bause avatar lucidsamuel avatar mcpizza0 avatar peerrich avatar riccardoperra avatar rishi-raj-jain avatar ryuapp avatar shentschel avatar shibukawa avatar sojinsamuel avatar testwill avatar tobihans avatar tungbq avatar vishalkhoje avatar wttw avatar zhaoyii avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

hanko's Issues

Rate limiting (application layer)

Currently we don't have a backoff strategy for passwords and passcodes, but we should implement one.

Proposal:

Passwords:
After 3 "free" tries the backoff takes over. (exponential or linear should be discussed)
Why "free" tries, because sometimes a user types in the wrong password, but immediately remembers and types in the correct one, when the user must wait then, he will be annoyed.

Passcodes:
Each passcode has 3 tries to be entered correctly. After that a new passcode must be requested.
Requesting the second (third?) passcodes will trigger the backoff strategy.

iOS Well-know route

Checklist

  • I could not find a solution in the existing issues or docs.
  • I agree to follow this project's Code of Conduct.

Description

Is there a built in way to add a well-known route for AASA?

Describe your ideal solution

No response

Workarounds or alternatives

No response

Hanko Version

0.3

Additional Context

No response

Update third_party handler test

Checklist

  • I could not find a solution in the existing issues or docs.
  • I agree to follow this project's Code of Conduct.

Description

Currently the test uses a test persister, which mocks the persister we use in the handler. But it does not provides all functionality to test the handler completely and it is hard to maintain.

Describe your ideal solution

Change the test, that need a database, to integration tests, using github.com/ory/dockertest. One handler test is already using it, check it out: user_test.go.

If a test needs a database, it should be skipped if the -short flag is provided. So even if someone has no docker installed, he can run some tests.

Workarounds or alternatives

No response

Hanko Version

all

Additional Context

No response

`session.cookie.domain` not being used correctly

Checklist

  • I could not find a solution in the existing issues or docs.
  • I agree to follow this project's Code of Conduct.

Describe the bug

I am using Hanko for my project Codefarem. It is live on the subdomain https://codefarem.ignisda.tech. Since it uses a subdomain, I have set session.cookie.domain to .codefarem.ignisda.tech. However, when the /finalize endpoint is called, it sends the domain as codefarem.ignisda.tech.
image

As a result, the cookie is not set and I immediately get this error: Your session has expired. Please log in again.

TLDR: The leading . is not respected.

Reproducing the bug

You can try to sign up on https://codefarem.ignisda.tech/auth to see the above problem.

Logs

2022-12-04T04:20:20.990713489Z app[web.1]: 2022/12/04 04:20:20 Using config file: ./config/config.yaml
2022-12-04T04:20:20.996329093Z app[web.1]: 2022/12/04 04:20:20 migrate up
2022-12-04T04:20:21.403762499Z app[web.1]: [POP] 2022/12/04 04:20:21 info - Migrations already up to date, nothing to apply
2022-12-04T04:20:21.404357581Z app[web.1]: [POP] 2022/12/04 04:20:21 info - 0.4063 seconds
2022-12-04T04:20:21.519800089Z app[web.1]: 2022/12/04 04:20:21 Using config file: ./config/config.yaml
2022-12-04T04:20:21.712076042Z app[web.1]: ⇨ http server started on [::]:5000
2022-12-04T04:20:53.978966815Z app[web.1]: {"time":"2022-12-04T04:20:53.977793706Z","time_unix":"1670127653","id":"WPkq3flobQc5g4me7d6HO28FTbUcfTOs","remote_ip":"103.79.169.10","host":"codefarem--authenticator.ignisda.tech","method":"OPTIONS","uri":"/.well-known/config","user_agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36","status":204,"error":"","latency":7064605,"latency_human":"7.064605ms","bytes_in":0,"bytes_out":0,"referer":"https://codefarem.ignisda.tech/"}
2022-12-04T04:20:54.132266532Z app[web.1]: {"time":"2022-12-04T04:20:54.131768573Z","time_unix":"1670127654","id":"FxX7JLUu7eoz4WHuJaTSBnL2hKUzSIj0","remote_ip":"103.79.169.10","host":"codefarem--authenticator.ignisda.tech","method":"GET","uri":"/.well-known/config","user_agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36","status":200,"error":"","latency":15354677,"latency_human":"15.354677ms","bytes_in":0,"bytes_out":55,"referer":"https://codefarem.ignisda.tech/"}
2022-12-04T04:20:54.197669256Z app[web.1]: {"time":"2022-12-04T04:20:54.197325709Z","time_unix":"1670127654","id":"4fHFH4EKOUZpeDMNHJn1zb33y0Ffnpll","remote_ip":"103.79.169.10","host":"codefarem--authenticator.ignisda.tech","method":"OPTIONS","uri":"/me","user_agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36","status":204,"error":"","latency":24101,"latency_human":"24.101µs","bytes_in":0,"bytes_out":0,"referer":"https://codefarem.ignisda.tech/"}
2022-12-04T04:20:54.244449196Z app[web.1]: {"time":"2022-12-04T04:20:54.244288554Z","time_unix":"1670127654","id":"VXF0TC2l9HK6xYO9eu12rsi05IjXl5Kj","remote_ip":"103.79.169.10","host":"codefarem--authenticator.ignisda.tech","method":"GET","uri":"/me","user_agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36","status":400,"error":"code=400, message=missing or malformed jwt","latency":3280544,"latency_human":"3.280544ms","bytes_in":0,"bytes_out":50,"referer":"https://codefarem.ignisda.tech/"}
2022-12-04T04:21:02.622613532Z app[web.1]: {"time":"2022-12-04T04:21:02.622447566Z","time_unix":"1670127662","id":"gFqkL8CgCDIJ70Ch7x1V9hjTC3RrJCKP","remote_ip":"103.79.169.10","host":"codefarem--authenticator.ignisda.tech","method":"OPTIONS","uri":"/user","user_agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36","status":204,"error":"","latency":41210,"latency_human":"41.21µs","bytes_in":0,"bytes_out":0,"referer":"https://codefarem.ignisda.tech/"}
2022-12-04T04:21:02.785348796Z app[web.1]: {"time":"2022-12-04T04:21:02.785177201Z","time_unix":"1670127662","id":"iecjyyV8Z4QSll149jagCq5eVOwEDeCl","remote_ip":"103.79.169.10","host":"codefarem--authenticator.ignisda.tech","method":"POST","uri":"/user","user_agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36","status":200,"error":"","latency":100436778,"latency_human":"100.436778ms","bytes_in":39,"bytes_out":94,"referer":"https://codefarem.ignisda.tech/"}
2022-12-04T04:21:02.906922710Z app[web.1]: {"time":"2022-12-04T04:21:02.906510593Z","time_unix":"1670127662","id":"eqJVw9PhS4hw2IxYzvVoKx5andQG3E3d","remote_ip":"103.79.169.10","host":"codefarem--authenticator.ignisda.tech","method":"OPTIONS","uri":"/passcode/login/initialize","user_agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36","status":204,"error":"","latency":33630,"latency_human":"33.63µs","bytes_in":0,"bytes_out":0,"referer":"https://codefarem.ignisda.tech/"}
2022-12-04T04:21:06.081752558Z app[web.1]: {"time":"2022-12-04T04:21:06.081568289Z","time_unix":"1670127666","id":"DFYs1YVmRLLEbS9DdKzWwz6VAQiFOosW","remote_ip":"103.79.169.10","host":"codefarem--authenticator.ignisda.tech","method":"POST","uri":"/passcode/login/initialize","user_agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36","status":200,"error":"","latency":3116827230,"latency_human":"3.11682723s","bytes_in":50,"bytes_out":102,"referer":"https://codefarem.ignisda.tech/"}
2022-12-04T04:21:12.784945353Z app[web.1]: {"time":"2022-12-04T04:21:12.784781264Z","time_unix":"1670127672","id":"XohMZdNWWHvMYfqe1G66ygidYi0fOezA","remote_ip":"103.79.169.10","host":"codefarem--authenticator.ignisda.tech","method":"OPTIONS","uri":"/passcode/login/finalize","user_agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36","status":204,"error":"","latency":66501,"latency_human":"66.501µs","bytes_in":0,"bytes_out":0,"referer":"https://codefarem.ignisda.tech/"}
2022-12-04T04:21:13.131327206Z app[web.1]: {"time":"2022-12-04T04:21:13.131192784Z","time_unix":"1670127673","id":"qxQ4MAsrU52GTlVKpLOiOjzJO8AS3ItL","remote_ip":"103.79.169.10","host":"codefarem--authenticator.ignisda.tech","method":"POST","uri":"/passcode/login/finalize","user_agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36","status":200,"error":"","latency":303755999,"latency_human":"303.755999ms","bytes_in":61,"bytes_out":99,"referer":"https://codefarem.ignisda.tech/"}
2022-12-04T04:21:13.208364260Z app[web.1]: {"time":"2022-12-04T04:21:13.208209787Z","time_unix":"1670127673","id":"46gg7F8N0mtRkkUpgPdviYKB44UhxRv3","remote_ip":"103.79.169.10","host":"codefarem--authenticator.ignisda.tech","method":"OPTIONS","uri":"/me","user_agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36","status":204,"error":"","latency":43679,"latency_human":"43.679µs","bytes_in":0,"bytes_out":0,"referer":"https://codefarem.ignisda.tech/"}
2022-12-04T04:21:13.353965933Z app[web.1]: {"time":"2022-12-04T04:21:13.353808251Z","time_unix":"1670127673","id":"tjVhAxFrSm2lCHjQCUvTVPfWy3c8oIUm","remote_ip":"103.79.169.10","host":"codefarem--authenticator.ignisda.tech","method":"GET","uri":"/me","user_agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36","status":400,"error":"code=400, message=missing or malformed jwt","latency":952281,"latency_human":"952.281µs","bytes_in":0,"bytes_out":50,"referer":"https://codefarem.ignisda.tech/"}

Configuration

database:
  dialect: postgres
  user: '<redacted>'
  password: '<redacted>'
  host: '<redacted>'
  port: <redacted>
  database: '<redacted>'

passcode:
  email:
    from_address: '<redacted>'
    from_name: 'Codefarem'
  smtp:
    host: '<redacted>'
    port: <redacted>
    user: '<redacted>'
    password: '<redacted>'

secrets:
  keys:
    - '<redacted>'

service:
  name: 'Codefarem'

server:
  public:
    address: ':5000'
    cors:
      enabled: true
      allow_credentials: true
      allow_origins:
        - '*'

session:
  lifespan: '24h'
  cookie:
    domain: '.codefarem.ignisda.tech'
    http_only: true
    secure: true

Hanko Version

v0.3.1

OS

Linux

OS Version

Linux main 5.4.0-131-generic #147-Ubuntu SMP Fri Oct 14 17:07:22 UTC 2022 x86_64 x86_64 x86_64 GNU/Linux

Browser Version

Happens on all browsers

Environment

Docker

Additional Context

I looked into how https://example.hanko.io works:

image

It sends the Domain attribute the same way yet it works fine. Is there something I am doing wrong?

Backend configuration through environment variables not possible for multi-word, underline delimited keys

The backend configuration does not recognize environment variables where a key consists of multiple "words" delimited by an underscore.

Expected Behavior

When running the application I expect environment variables to be applied even if a key contains multiple "words" delimited by an underscore, i.e. when running WEBAUTHN_RELYING_PARTY=http://localhost:8080 ./hanko serve public I expect the given value to be applied to the config parameter that would be defined in the YAML configuration as webauthn.relying_party.

Current Behavior

The value is not applied because when loading the config any underscore is replaced by a dot, hence demarcating even underscores in a single key as equivalent to a level of nesting.

Update email handler test

Checklist

  • I could not find a solution in the existing issues or docs.
  • I agree to follow this project's Code of Conduct.

Description

Currently the test uses a test persister, which mocks the persister we use in the handler. But it does not provides all functionality to test the handler completely and it is hard to maintain.

Describe your ideal solution

Change the test, that need a database, to integration tests, using github.com/ory/dockertest. One handler test is already using it, check it out: user_test.go.

If a test needs a database, it should be skipped if the -short flag is provided. So even if someone has no docker installed, he can run some tests.

Workarounds or alternatives

No response

Hanko Version

all

Additional Context

No response

Frontend seems to expect 10-character passwords, regardless of backend config

Checklist

  • I could not find a solution in the existing issues or docs.
  • I agree to follow this project's Code of Conduct.

Describe the bug

With passwords enabled, but no min length set, the frontend shows a min length requirement of 10 characters for a new password. The default value of the backend is 8 characters, thought. It seems like the frontend is not in sync with the backend setting.

Reproducing the bug

  1. Enable passwords, but set no min length requirement
  2. Create a new account through the UI
  3. On the view to set a password, the min length requirement of 10 characters is enforced

Logs

No response

Configuration

passwords:
  enable: true

Hanko Version

0.2.0

OS

No response

OS Version

No response

Environment

No response

Additional Context

No response

Update password handler test

Checklist

  • I could not find a solution in the existing issues or docs.
  • I agree to follow this project's Code of Conduct.

Description

Currently the test uses a test persister, which mocks the persister we use in the handler. But it does not provides all functionality to test the handler completely and it is hard to maintain.

Describe your ideal solution

Change the test, that need a database, to integration tests, using github.com/ory/dockertest. One handler test is already using it, check it out: user_test.go.

If a test needs a database, it should be skipped if the -short flag is provided. So even if someone has no docker installed, he can run some tests.

Workarounds or alternatives

No response

Hanko Version

all

Additional Context

No response

Conditional UI stops working on error

Checklist

  • I could not find a solution in the existing issues or docs.
  • I agree to follow this project's Code of Conduct.

Describe the bug

When trying to sign in via Conditional UI and an error occurs, Conditional UI cannot be used anymore.

Reproducing the bug

  • Create two passkeys on the same device
  • Delete one of the passkeys on the server
  • Try to log in with the deleted passkey (that still exists on the device)
  • An error is shown ("Invalid credential")
  • Conditional is not offered anymore

Hanko Version

0.3.1

OS

Windows

OS Version

Win 11 22H2

Browser Version

Chrome 108

6 digits passcode can be brute forced

Hi! Great work so far. I saw that you use a 6 digit passcode which has just 1 million possibilities. https://github.com/teamhanko/hanko/blob/main/backend/crypto/passcode.go

Without a rate limit this can be brute forced by trying out all combinations.
I saw that rate limits have to be added by the user, however most IP based rate limits can be bypassed using proxy services. Using only a rate limit is only one layer of defense.

A more secure solution would be to use letters as well so the code is not so easily guessable. That way there is some “defense in depth”.

Translations in french in frontend elements

Checklist

  • I could not find a solution in the existing issues or docs.
  • I agree to follow this project's Code of Conduct.

Description

Adding the french local to the translations object of frontend/elements

Describe your ideal solution

That the folllowing file:

  • teamhanko/hanko/frontend/elements/src/Translations.ts
    Gets a "fr" attribute for the french language.

Workarounds or alternatives

Here is a translation I would use. Frankly, I used DeepL and reviewed all translation to use consistent words and respect the semantics in auth/web...

export const fr_translations = {
    fr: {
      headlines: {
        error: "Une erreur est survenue",
        loginEmail: "S'identifier ou s'inscrire",
        loginFinished: "Connexion réussie",
        loginPasscode: "Saisissez le code d'accès",
        loginPassword: "Saisissez le mot de passe",
        registerAuthenticator: "Enregistrer une clé d'accès",
        registerConfirm: "Créer un compte ?",
        registerPassword: "Définir un nouveau mot de passe",
        profileEmails: "E-mails",
        profilePassword: "Mot de passe",
        profilePasskeys: "Clés d'accès",
        isPrimaryEmail: "Adresse électronique principale",
        setPrimaryEmail: "Définir l'adresse e-mail principale",
        emailVerified: "Vérifié",
        emailUnverified: "Non vérifié",
        emailDelete: "Supprimer",
        renamePasskey: "Renommer la clé d'accès",
        deletePasskey: "Supprimer la clé d'accès",
        lastUsedAt: "Dernière utilisation à",
        createdAt: "Créé à",
        connectedAccounts: "Comptes connectés",
      },
      texts: {
        enterPasscode: 'Entrez le code d\'accès qui a été envoyé à"{emailAddress}".',
        setupPasskey:
          "Connectez-vous à votre compte de manière simple et sécurisée avec une clé d'accès. Remarque : vos données biométriques sont uniquement stockées sur vos appareils et ne seront jamais partagées avec quiconque.",
        createAccount:
          'Il n\'existe pas de compte pour"{emailAddress}". Voulez-vous créer un nouveau compte ?',
        passwordFormatHint:
          "Doit être compris entre {minLength} et {maxLength} caractères.",
        manageEmails:
          "Vos adresses e-mails sont utilisées pour la communication et l'authentification.",
        changePassword: "Définissez un nouveau mot de passe.",
        managePasskeys: "Vos clé d'accès vous permettent de vous connecter à ce compte.",
        isPrimaryEmail:
          "Utilisé pour la communication, les codes d'accès, et comme nom d'utilisateur pour les clés d'accès. Pour changer l'adresse e-mail primaire, ajoutez d'abord une autre adresse e-mail et définissez-la comme primaire.",
        setPrimaryEmail:
          "Définissez cette adresse e-mail comme primaire afin qu'elle soit utilisée pour les communications, pour les codes d'accès et comme nom d'utilisateur pour les clés d'accès.",
        emailVerified: "Cette adresse e-mail a été vérifiée.",
        emailUnverified: "Cette adresse électronique n'a pas été vérifiée.",
        emailDelete:
          "Si vous supprimez cette adresse e-mail, elle ne pourra plus être utilisée pour vous connecter à votre compte. Les clés d'accès qui ont pu être créées avec cette adresse électronique resteront intactes.",
        emailDeleteThirdPartyConnection:
          "Si vous supprimez cette adresse e-mail, elle ne pourra plus être utilisée pour vous connecter. Vous ne pouvez plus non plus vous connecter avec votre compte {provider}. Les clés d'accès qui ont pu être créées avec cette adresse e-mail resteront intactes.",
        emailDeletePrimary:
          "L'adresse e-mail principale ne peut pas être supprimée. Ajoutez d'abord une autre adresse e-mail et désignez là comme principale.",
        renamePasskey:
          "Définissez un nom pour la clé d'accès qui vous guide pour identifier l'endroit où elle est stockée.",
        deletePasskey:
          "Supprimez cette clé d'accès de votre compte. Notez que la clé d'accès existe toujours sur vos appareils et qu'elle doit être supprimée là-bas également.",
      },
      labels: {
        or: "ou",
        email: "E-mail",
        continue: "Continuer",
        skip: "Sauter",
        save: "Enregistrer",
        password: "Mot de passe",
        signInPassword: "Se connecter avec un mot de passe",
        signInPasscode: "Se connecter avec un code d'accès",
        forgotYourPassword: "Vous avez oublié votre mot de passe ?",
        back: "Retour",
        signInPasskey: "Se connecter avec une clé d'accès",
        registerAuthenticator: "Enregistrer une clé d'accès",
        signIn: "Se connecter",
        signUp: "S'inscrire",
        sendNewPasscode: "Envoyer un nouveau code",
        passwordRetryAfter: "Réessayer dans{passwordRetryAfter}",
        passcodeResendAfter: "Demandez un nouveau code dans{passcodeResendAfter}",
        unverifiedEmail: "non vérifié",
        primaryEmail: "primaire",
        setAsPrimaryEmail: "Définir comme primaire",
        verify: "Vérifier",
        delete: "Supprimer",
        newEmailAddress: "Nouvelle adresse e-mail",
        newPassword: "Nouveau mot de passe",
        rename: "Renommer",
        newPasskeyName: "Nouveau nom de clé",
        addEmail: "Ajouter une adresse e-mail",
        changePassword: "Modifier le mot de passe",
        addPasskey: "Ajouter une clé d'accès",
        webauthnUnsupported: "Les clés d'accès ne sont pas prises en charge par votre navigateur",
        signInWith: "Connectez-vous avec{provider}",
      },
      errors: {
        somethingWentWrong:
          "Une erreur technique s'est produite. Veuillez réessayer plus tard.",
        requestTimeout: "La requête a expirée.",
        invalidPassword: "E-mail ou mot de passe erronés.",
        invalidPasscode: "Le code d'accès fourni était incorrect.",
        passcodeAttemptsReached:
          "Le code d'accès a été saisi de manière incorrecte plusieurs fois. Veuillez demander un nouveau code.",
        tooManyRequests:
          "Trop de requêtes ont été faites. Veuillez attendre pour répéter l'opération demandée.",
        unauthorized: "Votre session a expiré. Veuillez vous reconnecter.",
        invalidWebauthnCredential: "Cette clé d'accès ne peut plus être utilisée.",
        passcodeExpired: "Le code d'accès a expiré. Veuillez en demander un nouveau.",
        userVerification:
          "Vérification de l'utilisateur requise. Veuillez vous assurer que votre appareil d'authentification est protégé par un code PIN ou biométrique.",
        emailAddressAlreadyExistsError: "L'adresse e-mail existe déjà.",
        maxNumOfEmailAddressesReached: "Aucune autre adresse e-mail ne peut être ajoutée.",
        thirdPartyAccessDenied:
          "Accès refusé. La demande a été annulée par l'utilisateur ou le fournisseur a refusé l'accès pour d'autres raisons.",
        thirdPartyMultipleAccounts:
          "Impossible d'identifier le compte. L'adresse e-mail est utilisée par plusieurs comptes.",
        thirdPartyUnverifiedEmail:
          "Vérification de l'adresse e-mail requise. Veuillez vérifier l'adresse e-mail utilisée auprès de votre fournisseur.",
      },
    },
  }; 

Hanko Version

0.1.2-alpha

Additional Context

No response

Update passcode handler test

Checklist

  • I could not find a solution in the existing issues or docs.
  • I agree to follow this project's Code of Conduct.

Description

Currently the test uses a test persister, which mocks the persister we use in the handler. But it does not provides all functionality to test the handler completely and it is hard to maintain.

Describe your ideal solution

Change the test, that need a database, to integration tests, using github.com/ory/dockertest. One handler test is already using it, check it out: user_test.go.

If a test needs a database, it should be skipped if the -short flag is provided. So even if someone has no docker installed, he can run some tests.

Workarounds or alternatives

No response

Hanko Version

all

Additional Context

No response

(example): not friendly UX when trying to sign in before registering

Clicking on Sign in with passkey before creating a user or registering the device causes confusion.

The example client will get the publicKey.challenge and initiate the process before requesting an email and checking if we do have user/device registered first.

Before a user is created:
Screenshot 2022-06-16 at 16 55 40

After a user is created:
Screenshot 2022-06-16 at 16 54 52

Solution:
BestBuy flow seems reasonable, you can do email login flow, or you click on Sign in with WebAuthn, it routes to a form to fill in the email first, checks that it's possible, respond with an error that it's not possible till it's setup first.

Screenshot 2022-06-16 at 16 47 58

Screenshot 2022-06-16 at 16 48 05

Screenshot 2022-06-16 at 16 47 44

ENOENT: no such file or directory, lstat 'node_modules/@teamhanko/frontend-sdk'

Checklist

  • I could not find a solution in the existing issues or docs.
  • I agree to follow this project's Code of Conduct.

Describe the bug

I am trying to install @teamhanko/frontend-sdk in my project using yarn. But I am getting the above error:

Reproducing the bug

Simply run yarn install @teamhanko/hanko-elements

Logs

image

Configuration

No response

Hanko Version

v0.3.2

OS

Linux

OS Version

No response

Browser Version

No response

Environment

None

Additional Context

It is probably due to this line

"@teamhanko/hanko-frontend-sdk": "file:../frontend-sdk",
.

Make e2e test check for component ids instead of button texts etc.

Changing e.g. a button text in hanko-auth currently breaks e2e tests because the tests are based on strings ("see if button with text 'Sign in with a passkey" appears").

Wouldn't it be better to set ids for all relevant components in the element and make the tests check for the ids?

Typo in docs

Checklist

  • I could not find a solution in the existing issues or docs.
  • I agree to follow this project's Code of Conduct.

Describe the bug

Both HankoAuth.jsx code snippets in https://docs.hanko.io/guides/react have a trailing '!' at the end of 'REACT_APP_HANKO_API!'

The React example also includes a Next.js component.

--

Reproducing the bug

N/A

Logs

No response

Configuration

No response

Hanko Version

n/a

OS

No response

OS Version

No response

Browser Version

No response

Environment

No response

Additional Context

No response

hanko-elements lang not optional

Checklist

  • I could not find a solution in the existing issues or docs.
  • I agree to follow this project's Code of Conduct.

Describe the bug

The doc says:

lang Currently supported values are "en" for English and "de" for German. If the value is omitted, "en" is used.

Tried to update the examples but they wouldn't work with some undefined error.

Providing the lang parameter fixes this.

Reproducing the bug

"@teamhanko/hanko-elements": "^0.1.1-alpha"

Logs

elements.js:2 Uncaught TypeError: Cannot read properties of undefined (reading 'toString')
    at t.default [as constructor] (elements.js:2:1)
    at y.L [as render] (elements.js:2:1)
    at I (elements.js:2:1)
    at O (elements.js:2:1)
    at I (elements.js:2:1)
    at O (elements.js:2:1)
    at I (elements.js:2:1)
    at O (elements.js:2:1)
    at I (elements.js:2:1)
    at U (elements.js:2:1)
t.default @ elements.js:2
L @ elements.js:2
I @ elements.js:2
O @ elements.js:2
I @ elements.js:2
O @ elements.js:2
I @ elements.js:2
O @ elements.js:2
I @ elements.js:2
U @ elements.js:2
l @ elements.js:2
a @ elements.js:2
(anonymous) @ elements.js:2
(anonymous) @ elements.js:2
a @ elements.js:2
d @ elements.js:2
(anonymous) @ elements.js:2
(anonymous) @ elements.js:2
a @ elements.js:2
t.register @ elements.js:2
(anonymous) @ HankoAuth.tsx:16
commitHookEffectListMount @ react-dom.development.js:23150
commitPassiveMountOnFiber @ react-dom.development.js:24926
commitPassiveMountEffects_complete @ react-dom.development.js:24891
commitPassiveMountEffects_begin @ react-dom.development.js:24878
commitPassiveMountEffects @ react-dom.development.js:24866
flushPassiveEffectsImpl @ react-dom.development.js:27039
flushPassiveEffects @ react-dom.development.js:26984
(anonymous) @ react-dom.development.js:26769
workLoop @ scheduler.development.js:266
flushWork @ scheduler.development.js:239
performWorkUntilDeadline @ scheduler.development.js:533

Configuration

No response

Hanko Version

"@teamhanko/hanko-elements": "^0.1.1-alpha"

OS

None

OS Version

No response

Browser Version

No response

Environment

None

Additional Context

No response

Passkeys.io contradicts itself

Checklist

  • I could not find a solution in the existing issues or docs.
  • I agree to follow this project's Code of Conduct.

Describe the bug

Cannot use the demo without a valid email address entered (or 1:1,000,000 guess)!


Create an account with a passkey
1. Enter your email address* and hit Continue.
‍
2. Follow the prompts to create an account and a passkey.
‍
* You can use a fake email address, but then you won't be able to access the account without the passkey. If you want to create multiple accounts, you can just add +n to your email address, e.g., [email protected]

Reproducing the bug

  1. Go to https://www.passkeys.io/
  2. Enter dummy email address
  3. Cannot create credentials.

Logs

No response

Configuration

No response

Hanko Version

33355eb3-9429-4a18-8dfe-f17f7e86300b-js.hanko.io

OS

No response

OS Version

No response

Browser Version

No response

Environment

No response

Additional Context

No response

Document behavior of hanko-auth depending on device capabilities

We need to document our implementation / decision algorithm of <hanko-auth>:

  • Passkey creation and login is only offered if supported on the current device
  • The fallback is always the email passcode (or a password if enabled in the backend config)
  • Android has no full passkey support yet, so the username is always required, but then a WebAuthn auth is possible
  • On latest iOS, macOS and Windows, the "Sign in with a passkey" button is shown, meaning you can also login without having to enter the username
  • There may be more that I am missing here

Too technical error message when trying to sign in with an invalid passkey

Checklist

  • I could not find a solution in the existing issues or docs.
  • I agree to follow this project's Code of Conduct.

Describe the bug

When trying to use a passkey that is stored on the device but not on the server, the following error message is shown in hanko-auth:
image

I find the random appearance of "WebAuthn" not in line with the passkey wording everywhere else. Saying "credentials" does not help either. Both are technical terms that the typical user does not need to know.

Rephrase the message, e.g., "This passkey cannot be used anymore"

Reproducing the bug

  1. Create a new user
  2. Create a passkey
  3. Logout
  4. Delete passkey (clear db)
  5. Attempt to sign in with passkey from device

Logs

No response

Configuration

No response

Hanko Version

0.2.0

OS

macOS

OS Version

macOS 13 Beta

Environment

No response

Additional Context

No response

Golang / Flutter integration

I build gui apps for web, desktop and mobile in golang using gioui. It’s like flutter but in golang.

I think it’s possible to build a package that works with Hanko if your interested.

It would be cool for the gioui community to have a hanko integration.

https://github.com/gioui/gio-x/tree/main/notify is an example of OS native calls. Same sort of thing for Passkey calls etc.

it should be possible to work out the right OS calls for iOS, android.

There is a webview for gio also which I guess is needed for desktop, unless there are Passkey api for desktop is we can tap into?

For web, a golang wasm can do it to call the sone js that then calls the correct Browser api.

Gioui works very well on mobile and desktop.

Update webauthn handler test

Checklist

  • I could not find a solution in the existing issues or docs.
  • I agree to follow this project's Code of Conduct.

Description

Currently the test uses a test persister, which mocks the persister we use in the handler. But it does not provides all functionality to test the handler completely and it is hard to maintain.

Describe your ideal solution

Change the test, that need a database, to integration tests, using github.com/ory/dockertest. One handler test is already using it, check it out: user_test.go.

If a test needs a database, it should be skipped if the -short flag is provided. So even if someone has no docker installed, he can run some tests.

Workarounds or alternatives

No response

Hanko Version

all

Additional Context

No response

passkeys.io: Timeout when signing in with Chrome after account setup on iOS

Checklist

  • I could not find a solution in the existing issues or docs.
  • I agree to follow this project's Code of Conduct.

Describe the bug

I created an account on my iPhone and then tried to sign in on Chrome on macOS. I selected "Use phone with a QR code", scanned the code with my iPhone, and selected "Sign in with a Passkey" on my iPhone, but then the sign-in process just timed out. Bluetooth and WiFi are enabled on both devices.

Reproducing the bug

See above

Logs

No response

Configuration

No response

Hanko Version

None

OS

No response

OS Version

No response

Browser Version

No response

Environment

No response

Additional Context

No response

elements: headline wrap not styled properly

Checklist

  • I could not find a solution in the existing issues or docs.
  • I agree to follow this project's Code of Conduct.

Describe the bug

If headlines are wrapped, e.g. on smaller devices or when increasing the font size, the resulting style is not optimal:

image

Reproducing the bug

Visit e.g. passkeys.io on a device with less than 360px display width. This can easily be reproduced in Chrome dev tools.

Logs

No response

Configuration

No response

Hanko Version

0.2.0

OS

No response

OS Version

No response

Browser Version

No response

Environment

No response

Additional Context

No response

Evaluate removal of constraints for platform authenticators

Checklist

  • I could not find a solution in the existing issues or docs.
  • I agree to follow this project's Code of Conduct.

Description

During registration, authenticatorAttachment is set to platform and therefore only platform authenticators can be used to register a passkey. But as the platforms/browsers behave inconsistently (Safari shows hybrid option while chrome does not), this effectively prevents hybrid passkey creation in Chrome (i.e., creating the passkey on a phone instead of on the platform).

There is also a check for isUserVerifyingPlatfomAuthenticatorAvailable in all places where WebAuthn could be triggered for the decision to show or hide the passkey login button / Conditional UI, again unnecessarily restricting usage of the hybrid option for authentication in cases where no platform authenticator is available but the browser is able to do the hybrid flow.

Describe your ideal solution

It should be evaluated if both constraints can be safely removed and leave handling different authenticator types to the client UI and the user. The only necessary check would be the availability of the WebAuthn API.

This way we can always support the hybrid option for registration and authentication.

Workarounds or alternatives

none

Hanko Version

0.2.0

Additional Context

A side effect of the above would be allowing passkey creation on physical Security Keys (SK). The full extent of this change must be thoroughly tested.

The current implementations of passkeys by Apple and Google indicate that passkeys always have to be discoverable credentials (or resident keys, RK), otherwise the UX of Conditional UI and/or a "Sign in with a passkey" button (without entering a username first) does not work.

So creating a passkey on a SK means we'd have to set residentKey: required on credential creation.

One thing we have to be aware of is that today's SKs only support a limited amount of RKs due to small storage space on the keys. That's why the residentKey: preferred option exists. But depending on the SK hardware, if we set (as we do now)

  • requireResidentKey: true (WebAuthn L1)
  • residentKey: preferred (L2),

the resulting "passkey" stored on the SK may not be a RK and therefore not usable in the typical usernameless passkey authentication flows implemented by the platforms, resulting in bad UX (user successfully creates a "passkey" on a SK, but cannot use it to sign in).

Aside from always requiring RKs, another possible solution for this could be that we continue to say preferred, but let Hanko backend check whether the newly created passkey credential has the RK flag and lets the "Save a passkey" flow fail if not. However, it is necessary to consider whether this approach has any advantages over simply requiring a RK

Update user admin handler test

Checklist

  • I could not find a solution in the existing issues or docs.
  • I agree to follow this project's Code of Conduct.

Description

Currently the test uses a test persister, which mocks the persister we use in the handler. But it does not provides all functionality to test the handler completely and it is hard to maintain.

Describe your ideal solution

Change the test, that need a database, to integration tests, using github.com/ory/dockertest. One handler test is already using it, check it out: user_test.go.

If a test needs a database, it should be skipped if the -short flag is provided. So even if someone has no docker installed, he can run some tests.

Workarounds or alternatives

No response

Hanko Version

all

Additional Context

No response

hanko-auth: username input label background buggy on dark themes

Checklist

  • I could not find a solution in the existing issues or docs.
  • I agree to follow this project's Code of Conduct.

Describe the bug

The username input label looks buggy on dark themes when the input becomes disabled.

image

Reproducing the bug

  1. Click "Sign in with a passkey"
  2. See result

Logs

No response

Configuration

No response

Hanko Version

0.3.0

OS

No response

OS Version

No response

Browser Version

No response

Environment

No response

Additional Context

No response

Simplify backend config

New echo version needs special logic to handle '*' origin in combination with Allow-Credentials.
Task:
Simplify the backend config regarding cors. Users should not be bothered with this.

elements: registration confirmation page not wrapping paragraph content per default

Checklist

  • I could not find a solution in the existing issues or docs.
  • I agree to follow this project's Code of Conduct.

Describe the bug

Really long user emails are not wrapped per default on the registration confirmation page paragraph message informing the user, that no account for the given email address exists (see screenshot in 'Additional context' below).

Reproducing the bug

  1. Run the quickstart
  2. In your browser, navigate to http://localhost:8888
  3. On the LoginEmail page (Sign in or sign up) enter an (obnoxiously) long email
  4. Click continue

Logs

No response

Configuration

No response

Hanko Version

558caeb

OS

macOS

OS Version

macOS Monterey 12.6

Browser Version

All browsers, all versions (I guess?)

Environment

Docker Compose

Additional Context

Tested with the quickstart, the problem seems to be present on all browsers regardless of the run environment though.

Registration_Confirmation_Unwrapped_Content

Can't sign up to passkeys.io with single phone

Checklist

  • I could not find a solution in the existing issues or docs.
  • I agree to follow this project's Code of Conduct.

Describe the bug

Trying out passkeys.io seems to require you to own two phones.

When you visit the site (on your phone) and try to sign up/in it shows a pop-up with a QR code that needs to be scanned - so you can't scan it with the device you're using.

Here is a video of the user experience: https://user-images.githubusercontent.com/862951/197876176-af066b20-a800-4a59-85ed-2f1b697e5a52.MOV

Reproducing the bug

  1. Visit passkeys.io on an iPhone running iOS 16.1.
  2. Try to sign in.
  3. Get offered a QR to scan.

Logs

No response

Configuration

No response

Hanko Version

passkeys.io demo of Hanko

OS

Other

OS Version

iOS 16.1

Browser Version

No response

Environment

No response

Additional Context

No response

Passkey overwritten in profile

          I just tried the following:
  • Run this PRs quickstart.
  • Login with an email I already registered via passkey with a previous hanko version
  • Add a passkey in the profile
  • Delete the passkey I just created
  • Logout
  • Try to log in again -> fails
  • When logging in again via passcode the "old" passkey is still shown there

Originally posted by @like-a-bause in #495 (comment)

Support `DATABASE_URL` environment variable

Checklist

  • I could not find a solution in the existing issues or docs.
  • I agree to follow this project's Code of Conduct.

Description

Configuring the config.yml with correct database parameters is very tedious.

Describe your ideal solution

The application should use the DATABASE_URL environment variable and if not found, fall back to the database settings in config.yml.

Workarounds or alternatives

#!/usr/bin/env sh

export DATABASE_USER=$(echo $DATABASE_URL | grep -oP "postgres://\K(.+?):" | cut -d: -f1)
export DATABASE_PASSWORD=$(echo $DATABASE_URL | grep -oP "postgres://.*:\K(.+?)@" | cut -d@ -f1)
export DATABASE_HOST=$(echo $DATABASE_URL | grep -oP "postgres://.*@\K(.+?):" | cut -d: -f1)
export DATABASE_PORT=$(echo $DATABASE_URL | grep -oP "postgres://.*@.*:\K(\d+)/" | cut -d/ -f1)
export DATABASE_NAME=$(echo $DATABASE_URL | grep -oP "postgres://.*@.*:.*/\K(.+?)$")

mkdir -p /config

(cat /config.yml) | envsub > /config/config.yaml

/hanko migrate up
/hanko serve public

Hanko Version

v0.3.1

Additional Context

No response

elements: compatibility with Svelte - attributes/properties not available on rerender

Checklist

  • I could not find a solution in the existing issues or docs.
  • I agree to follow this project's Code of Conduct.

Describe the bug

Using the hanko-auth web component in a Svelte application causes issues when the component is rendered.

A detailed description of the issue can be found here.

There is also an open pull request in the preact-custom-element project that might solve the issue. We also commented on this PR including a mention of one of the maintainers.

Reproducing the bug

  1. Checkout the branch feat-example-svelte
  2. Start the required applications
  3. Open frontend location in browser
  4. Create account/login
  5. Logout
  6. Observe error (see "Logs" below) in browser devtool console

Logs

element.hanko-auth.js:2 Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'props')
    at o2.get (element.hanko-auth.js:2:22790)
    at set_custom_element_data (index.mjs:478:29)
    at Object.create [as c] (Login.svelte? [sm]:4:49)
    at create_component (index.mjs:1821:20)
    at Object.create [as c] (App.svelte? [sm]:4:39)
    at Object.create [as c] (Route.svelte:117:24)
    at Object.create [as c] (Route.svelte:106:26)
    at Object.create [as c] (Router.svelte:204:65)
    at create_component (index.mjs:1821:20)
    at Object.create [as c] (Route.svelte:117:24)

Configuration

database:
  user: hanko
  password: hanko
  host: localhost
  port: 5432
  dialect: postgres
passcode:
  email:
    from_address: [email protected]
  smtp:
    host: localhost
    port: 2500
secrets:
  keys:
    - abcedfghijklmnopqrstuvwxyz
service:
  name: Hanko Authentication Service
server:
  public:
    cors:
      enabled: true
      allow_credentials: true
      allow_origins:
        - "*"
webauthn:
  relying_party:
    origin: "http://localhost:8888"

Hanko Version

0.2.0

OS

macOS

OS Version

Monterey (12.6 (21G115))

Environment

Binary/Build & Run from Source

Additional Context

Not all "subprojects" have the "global" version indicated by the repo tag. The svelte example in the feat-example-svelte branch currently uses @teamhanko/hanko-elements version 0.0.8-alpha which in turn uses @teamhanko/hanko-frontend-sdk version 0.0.5-alpha.

Return same HTTP Code for both passcode ID not found and passcode is incorrect

Currently, the backend returns an HTTP 404 NotFound if the passcode ID supplied in the request cannot be found and returns an HTTP 401 Unauthorized response if the passcode ID is correct but the code supplied is not. Most standard login forms return the same HTTP code (401) if either username or password are incorrect, and since passcodes essentially act as a username/password combination I suggest we return the same HTTP code for either scenario.

Thinking from an adversary's standpoint, letting them know whether a passcode ID exists helps them when brute forcing a bunch of passcode ID and passcode options.

Could not able to click Create PassKey button- ( Mozilla,Ubuntu 22.04.1 LTS)

Checklist

  • I could not find a solution in the existing issues or docs.
  • I agree to follow this project's Code of Conduct.

Describe the bug

After giving login to 8888 port , I got pass key after adding passkey i landed to My Profile but "Create a PassKey" Button at right is disable,
On Mouse over showing " Web Authn is not supporting to this browser"

image

Could not able to "Create Pass Key" - ( Mozilla, Ubuntu-20.04 LTS)

Reproducing the bug

git clone https://github.com/teamhanko/hanko.git
Then, in the newly created hanko folder, just run:

docker compose -f deploy/docker-compose/quickstart.yaml -p "hanko-quickstart" up --build

Logs

No response

Configuration

No response

Hanko Version

HANKO-Beta 0.3.0

OS

Other

OS Version

Distributor ID: Ubuntu
Description: Ubuntu 22.04.1 LTS
Release: 22.04

Browser Version

FireFox - 106.0.4

Environment

Docker Compose

Additional Context

No response

quickstart: provide compose files for all databases supported by backend db layer

Checklist

  • I could not find a solution in the existing issues or docs.
  • I agree to follow this project's Code of Conduct.

Description

The quickstart compose file/cluster currently uses postgres only. We should provide compose files for other supported DBMS/DB dialects supported by the backend/pop.

Describe your ideal solution

Additional compose files probably need not be full-fledged compose files but could be overrides instead, replacing only the DBMS layer of the quickstart cluster.

Workarounds or alternatives

None

Hanko Version

558caeb

Additional Context

No response

backend: empty sql result sets not captured by sql.ErrNoRows checks for mysql dialect

Checklist

  • I could not find a solution in the existing issues or docs.
  • I agree to follow this project's Code of Conduct.

Describe the bug

The backend has several checks on whether a pop error represents an empty result (err == sql.ErrNoRows). This check will return false when using mysql or mariadb/the mysql dialect. This leads to erroneous API responses on several endpoints, in fact it is possible that the application won't even start up due to the creation of the JWKs:

When using the mysql dialect this error will be an fmt.WrapError with a stringified value of mysql select one: sql: no rows in result set and hence does not equal the value sql: no rows in result set of sql.ErrNoRows. This results in the JWK manager returning an error, then leading to a panic on startup instead of properly generating the JWK.

Tested with (see also 'Reproducing the bug' below):

  • MySQL 5.7
  • MySQL 8.0.29
  • MariaDB 10.9.3

Reproducing the bug

  1. Install docker
  2. Run (one of):
# MySQL 5.7
docker run --name mysql57 -e MYSQL_USER=hanko -e MYSQL_PASSWORD=hanko -e MYSQL_DATABASE=hanko -e MYSQL_ROOT_PASSWORD=root -p 5432:3306 -d mysql:5.7

# MySQL 8.0.29 - latest tag version as of writing
docker run --name mysqllatest -e MYSQL_USER=hanko -e MYSQL_PASSWORD=hanko -e MYSQL_DATABASE=hanko -e MYSQL_ROOT_PASSWORD=root -p 5432:3306 -d mysql:latest

# MariaDB 10.9.3 - latest tag version as of writing
docker run --name mariadblatest -e MARIADB_USER=hanko -e MARIADB_PASSWORD=hanko -e MARIADB_DATABASE=hanko -e MARIADB_ROOT_PASSWORD=root -p 5432:3306 -d mariadb:latest
  1. In backend/config/config.yaml change database.dialect to mysql
  2. Apply migrations: go run main.go migrate up in backend dir
  3. Run the backend: go run main.go serve public in backend dir

Logs

❯ go run main.go serve public
2022/10/26 17:34:48 Using config file: ./config/config.yaml
panic: failed to create jwk manager: failed to get jwk: mysql select one: sql: no rows in result set

goroutine 26 [running]:
github.com/teamhanko/hanko/backend/server.NewPublicRouter(0x204ca80, {0x999e098, 0xc00064c4f0})
        /Users/lfl/Documents/Hanko/hanko/backend/server/public_router.go:41 +0x18f9
github.com/teamhanko/hanko/backend/server.StartPublic(0x204ca80, 0x0, {0x999e098, 0xc00064c4f0})
        /Users/lfl/Documents/Hanko/hanko/backend/server/server.go:11 +0x6c
created by github.com/teamhanko/hanko/backend/cmd/serve.NewServePublicCommand.func1
        /Users/lfl/Documents/Hanko/hanko/backend/cmd/serve/public.go:29 +0x185
exit status 2

Configuration

database:
  user: hanko
  password: hanko
  host: localhost
  port: 5432
  dialect: mysql

Hanko Version

558caeb

OS

macOS

OS Version

macOS Monterey 12.6

Browser Version

No response

Environment

Binary/Build & Run from Source

Additional Context

Possible solution:

Instead of checking on err == sql.ErrNoRows we could/should probably use errors.Is because it repeatedly unwraps errors (hence eventually leading to the sql: no rows in result set root cause).

Provide a `logout` method on the SDK

Checklist

  • I could not find a solution in the existing issues or docs.
  • I agree to follow this project's Code of Conduct.

Description

It would be great if the Hanko SDK provided a logout method that would remove the hanko_cookie from the frontend.

Describe your ideal solution

Ideally this can be a part of the UserClient.

Workarounds or alternatives

No response

Hanko Version

v0.3.2

Additional Context

No response

Dead link in documentation

Checklist

  • I could not find a solution in the existing issues or docs.
  • I agree to follow this project's Code of Conduct.

Describe the bug

Following the React + Hanko guide here https://docs.hanko.io/guides/react

This link 404s https://github.com/teamhanko/hanko/blob/main/elements/README.md#script

Reproducing the bug

Click the above link

Logs

No response

Configuration

No response

Hanko Version

n/a

OS

No response

OS Version

No response

Browser Version

No response

Environment

No response

Additional Context

No response

First attempt passkey setup fails on iPhone

Checklist

  • I could not find a solution in the existing issues or docs.
  • I agree to follow this project's Code of Conduct.

Describe the bug

When creating a passkey on iPhone the passkey will not save correctly on first attempt.

Note: this test was done on www.passkeys.io

Reproducing the bug

When creating a passkey on iPhone the passkey will not save correctly on first attempt. The iPhone UI states that the Passkey has been saved however the Hanko login window appears to just time out. Tapping “Set up a passkey” again will correctly set up the passkey.

Note: this test was done on www.passkeys.io

Logs

No response

Configuration

No response

Hanko Version

Unsure (most current version used on passkeys.io on October 20, 2022)

OS

No response

OS Version

No response

Browser Version

No response

Environment

No response

Additional Context

No response

Update well-known handler test

Checklist

  • I could not find a solution in the existing issues or docs.
  • I agree to follow this project's Code of Conduct.

Description

Currently the test uses a test persister, which mocks the persister we use in the handler. But it does not provides all functionality to test the handler completely and it is hard to maintain.

Describe your ideal solution

Change the test, that need a database, to integration tests, using github.com/ory/dockertest. One handler test is already using it, check it out: user_test.go.

If a test needs a database, it should be skipped if the -short flag is provided. So even if someone has no docker installed, he can run some tests.

Workarounds or alternatives

No response

Hanko Version

all

Additional Context

No response

WebAuthn - Add DELETE endpoint for WebAuthn Credentials

Checklist

  • I could not find a solution in the existing issues or docs.
  • I agree to follow this project's Code of Conduct.

Description

First of all: Hanko's efforts are great. Thank you very much! 👍

Context:
Currently, there is no option to delete a registered WebAuthn authenticator.
See: https://docs.hanko.io/api/public#tag/WebAuthn

User Story:
As a user, I would like to delete certain WebAuthn Credentials.

Describe your ideal solution

Add a DELETE endpoint for WebAuthn Credentials.

Workarounds or alternatives

Hanko Version

v0.3.2

Additional Context

No response

Cookie: Make Domain configurable

From https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#define_where_cookies_are_sent

The Domain attribute specifies which hosts can receive a cookie. If unspecified, the attribute defaults to the same host that set the cookie, excluding subdomains. If Domain is specified, then subdomains are always included.The Domain attribute specifies which hosts can receive a cookie. If unspecified, the attribute defaults to the same host that set the cookie, excluding subdomains. If Domain is specified, then subdomains are always included.

In the scenario that hanko runs on auth.hanko.io and has to manage sessions for hanko.io we have to set the domain attribute which in turn should be configurable.

SMS code support

Hello, I want to set up auth for my family members, whom cannot be trusted to keep their email account secure. In this case, SMS is the safest option. Is this on the roadmap at all?

docker compose quickstart: http://localhost:8888/secured does not allow the "Create a passkey" button to be pressed.

Checklist

  • I could not find a solution in the existing issues or docs.
  • I agree to follow this project's Code of Conduct.

Describe the bug

am trying the quick start and have it running with docker compose,. I signed up with my email ( and got the 2fa key via mailslurper gui ) but the redirect to the screen at http://localhost:8888/secured does not allow the "Create a passkey" button to be pressed.

Reproducing the bug

In deploy folder:
docker-compose -f deploy/docker-compose/quickstart.yaml -p "hanko-quickstart" up --build

go to http://localhost:8888 and signup with email.

goto http://localhost:8080/ and copy the 6 digit 2fa into the GUI.

it auths the 2fs and redirects me to http://localhost:8888/secured where the "Create a Passkey" button is not able to be pressed.

Logs

*  Executing task: docker logs --tail 1000 -f 75fac641c1718deb6dd080e52fb0415f950429740fa8f4b6e39613f590f7057d 

export PATH=/usr/local/opt/go/libexec/bin:$PATH
clear
2022/10/11 08:48:24 Using config file: /etc/config/config.yaml
⇨ http server started on [::]:8001
⇨ http server started on [::]:8000
{"time":"2022-10-11T08:56:46.102537Z","time_unix":"1665478606","id":"U2lX6KSbaBH1AdFfh0FsJNZBTJxWZnCZ","remote_ip":"172.18.0.1","host":"localhost:8000","method":"OPTIONS","uri":"/.well-known/config","user_agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Safari/605.1.15","status":204,"error":"","latency":1852000,"latency_human":"1.852ms","bytes_in":0,"bytes_out":0,"referer":"http://localhost:8888/"}
{"time":"2022-10-11T08:56:46.151752Z","time_unix":"1665478606","id":"XUPS31pD3Mu1mM6LcjZ9sStfeyxZnlbn","remote_ip":"172.18.0.1","host":"localhost:8000","method":"GET","uri":"/.well-known/config","user_agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Safari/605.1.15","status":200,"error":"","latency":5890000,"latency_human":"5.89ms","bytes_in":0,"bytes_out":55,"referer":"http://localhost:8888/"}
{"time":"2022-10-11T08:56:46.299488Z","time_unix":"1665478606","id":"0etz6dDXYkgeicjxx8iGFAIUWPStbawi","remote_ip":"172.18.0.1","host":"localhost:8000","method":"OPTIONS","uri":"/me","user_agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Safari/605.1.15","status":204,"error":"","latency":76000,"latency_human":"76µs","bytes_in":0,"bytes_out":0,"referer":"http://localhost:8888/"}
{"time":"2022-10-11T08:56:46.362332Z","time_unix":"1665478606","id":"C8tu1QJu3VaMEbzd9kTov9yVgVyPCWxn","remote_ip":"172.18.0.1","host":"localhost:8000","method":"GET","uri":"/me","user_agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Safari/605.1.15","status":400,"error":"code=400, message=missing or malformed jwt","latency":13753000,"latency_human":"13.753ms","bytes_in":0,"bytes_out":50,"referer":"http://localhost:8888/"}
{"time":"2022-10-11T09:10:24.836431Z","time_unix":"1665479424","id":"dogi1WeI9srE0YhF4et8QqUomFYs6Z7T","remote_ip":"172.18.0.1","host":"localhost:8000","method":"OPTIONS","uri":"/.well-known/config","user_agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Safari/605.1.15","status":204,"error":"","latency":280000,"latency_human":"280µs","bytes_in":0,"bytes_out":0,"referer":"http://localhost:8888/"}
{"time":"2022-10-11T09:10:24.845709Z","time_unix":"1665479424","id":"D5TDdHGgRa030Fqih5fbLF80w48ur7wP","remote_ip":"172.18.0.1","host":"localhost:8000","method":"GET","uri":"/.well-known/config","user_agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Safari/605.1.15","status":200,"error":"","latency":74000,"latency_human":"74µs","bytes_in":0,"bytes_out":55,"referer":"http://localhost:8888/"}
{"time":"2022-10-11T09:10:25.09264Z","time_unix":"1665479425","id":"uiz0ZGSgtwMoQJvfHPg3yK28NBK93sYP","remote_ip":"172.18.0.1","host":"localhost:8000","method":"OPTIONS","uri":"/me","user_agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Safari/605.1.15","status":204,"error":"","latency":70000,"latency_human":"70µs","bytes_in":0,"bytes_out":0,"referer":"http://localhost:8888/"}
{"time":"2022-10-11T09:10:25.224483Z","time_unix":"1665479425","id":"SSvVDRorAdUSMacE0Vf6G5oTrJRof9mz","remote_ip":"172.18.0.1","host":"localhost:8000","method":"GET","uri":"/me","user_agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Safari/605.1.15","status":400,"error":"code=400, message=missing or malformed jwt","latency":116000,"latency_human":"116µs","bytes_in":0,"bytes_out":50,"referer":"http://localhost:8888/"}
{"time":"2022-10-11T09:11:18.018275Z","time_unix":"1665479478","id":"VpD5Mis2fbotPTWnWS3y8lgljjmKbbOt","remote_ip":"172.18.0.1","host":"localhost:8000","method":"OPTIONS","uri":"/user","user_agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Safari/605.1.15","status":204,"error":"","latency":402000,"latency_human":"402µs","bytes_in":0,"bytes_out":0,"referer":"http://localhost:8888/"}
{"time":"2022-10-11T09:11:18.074268Z","time_unix":"1665479478","id":"ZsAbyEOAjvejH6OKKSd3lfTkb6uwoWVf","remote_ip":"172.18.0.1","host":"localhost:8000","method":"POST","uri":"/user","user_agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Safari/605.1.15","status":404,"error":"Not Found: user not found","latency":26018000,"latency_human":"26.018ms","bytes_in":28,"bytes_out":35,"referer":"http://localhost:8888/"}
{"time":"2022-10-11T09:11:23.448162Z","time_unix":"1665479483","id":"M8sDj8B9ai7Y6rlkMUMjZ5IrYei53TNb","remote_ip":"172.18.0.1","host":"localhost:8000","method":"OPTIONS","uri":"/users","user_agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Safari/605.1.15","status":204,"error":"","latency":51000,"latency_human":"51µs","bytes_in":0,"bytes_out":0,"referer":"http://localhost:8888/"}
{"time":"2022-10-11T09:11:23.472835Z","time_unix":"1665479483","id":"DiuVodzSUtN7Y3ZooAl40ksvBYTnGAJc","remote_ip":"172.18.0.1","host":"localhost:8000","method":"POST","uri":"/users","user_agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Safari/605.1.15","status":200,"error":"","latency":19230000,"latency_human":"19.23ms","bytes_in":28,"bytes_out":176,"referer":"http://localhost:8888/"}
{"time":"2022-10-11T09:11:23.507473Z","time_unix":"1665479483","id":"skQBeRfDPdthHe2boQ5A0LQNDsrY43C4","remote_ip":"172.18.0.1","host":"localhost:8000","method":"OPTIONS","uri":"/passcode/login/initialize","user_agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Safari/605.1.15","status":204,"error":"","latency":135000,"latency_human":"135µs","bytes_in":0,"bytes_out":0,"referer":"http://localhost:8888/"}
{"time":"2022-10-11T09:11:23.930303Z","time_unix":"1665479483","id":"XkGKT0XelnNSdACDjiJlriVULZJ3QAef","remote_ip":"172.18.0.1","host":"localhost:8000","method":"POST","uri":"/passcode/login/initialize","user_agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Safari/605.1.15","status":200,"error":"","latency":407220000,"latency_human":"407.22ms","bytes_in":50,"bytes_out":99,"referer":"http://localhost:8888/"}
{"time":"2022-10-11T09:11:55.045389Z","time_unix":"1665479515","id":"T4hK2f51uU0QY5hPH3kqwtJR0M77zaCU","remote_ip":"172.18.0.1","host":"localhost:8000","method":"OPTIONS","uri":"/passcode/login/finalize","user_agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Safari/605.1.15","status":204,"error":"","latency":54000,"latency_human":"54µs","bytes_in":0,"bytes_out":0,"referer":"http://localhost:8888/"}
{"time":"2022-10-11T09:11:55.499244Z","time_unix":"1665479515","id":"EDmuGxVRuocyyyvZgUSiedaCF7U8QJLw","remote_ip":"172.18.0.1","host":"localhost:8000","method":"POST","uri":"/passcode/login/finalize","user_agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Safari/605.1.15","status":200,"error":"","latency":446992000,"latency_human":"446.992ms","bytes_in":61,"bytes_out":99,"referer":"http://localhost:8888/"}
{"time":"2022-10-11T09:11:55.513373Z","time_unix":"1665479515","id":"WmIgTr8DSwQFCysbbhCKP0J7B41WIHzm","remote_ip":"172.18.0.1","host":"localhost:8000","method":"OPTIONS","uri":"/me","user_agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Safari/605.1.15","status":204,"error":"","latency":85000,"latency_human":"85µs","bytes_in":0,"bytes_out":0,"referer":"http://localhost:8888/"}
{"time":"2022-10-11T09:11:55.520832Z","time_unix":"1665479515","id":"rPziGyb81yQbIR8GSEMahZpb7w2A3jt1","remote_ip":"172.18.0.1","host":"localhost:8000","method":"GET","uri":"/me","user_agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Safari/605.1.15","status":200,"error":"","latency":1265000,"latency_human":"1.265ms","bytes_in":0,"bytes_out":46,"referer":"http://localhost:8888/"}
{"time":"2022-10-11T09:11:55.532044Z","time_unix":"1665479515","id":"tLOuQbKOhlP0eDB3kTYgMfP9VqkypxUx","remote_ip":"172.18.0.1","host":"localhost:8000","method":"OPTIONS","uri":"/users/b208dbba-84a7-43cf-a3ad-3e0f4edd21a2","user_agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Safari/605.1.15","status":204,"error":"","latency":75000,"latency_human":"75µs","bytes_in":0,"bytes_out":0,"referer":"http://localhost:8888/"}
{"time":"2022-10-11T09:11:55.543142Z","time_unix":"1665479515","id":"rAtjWisXVaQ8qmJAQviDWAbHFekSKo8v","remote_ip":"172.18.0.1","host":"localhost:8000","method":"GET","uri":"/users/b208dbba-84a7-43cf-a3ad-3e0f4edd21a2","user_agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Safari/605.1.15","status":200,"error":"","latency":5252000,"latency_human":"5.252ms","bytes_in":0,"bytes_out":175,"referer":"http://localhost:8888/"}
{"time":"2022-10-11T09:11:56.112497Z","time_unix":"1665479516","id":"SevXnlznP5r4Dl3cjSau9nL0g8M8uMOC","remote_ip":"172.18.0.6","host":"hanko:8000","method":"GET","uri":"/.well-known/jwks.json","user_agent":"Go-http-client/1.1","status":200,"error":"","latency":4519000,"latency_human":"4.519ms","bytes_in":0,"bytes_out":797,"referer":""}
{"time":"2022-10-11T09:11:56.419236Z","time_unix":"1665479516","id":"VMY5Fj0thQBzriAcVpGSnMewM6f6B4Ao","remote_ip":"172.18.0.1","host":"localhost:8000","method":"GET","uri":"/me","user_agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Safari/605.1.15","status":200,"error":"","latency":5343000,"latency_human":"5.343ms","bytes_in":0,"bytes_out":46,"referer":"http://localhost:8888/"}
{"time":"2022-10-11T09:11:56.557699Z","time_unix":"1665479516","id":"TZZubTimPdckt16qzpg0NBQh1WED3QLv","remote_ip":"172.18.0.1","host":"localhost:8000","method":"GET","uri":"/users/b208dbba-84a7-43cf-a3ad-3e0f4edd21a2","user_agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Safari/605.1.15","status":200,"error":"","latency":11335000,"latency_human":"11.335ms","bytes_in":0,"bytes_out":175,"referer":"http://localhost:8888/"}
{"time":"2022-10-11T09:12:19.383571Z","time_unix":"1665479539","id":"hm6C4st8wCVWwMgD4LK6uZahUktvH1ik","remote_ip":"172.18.0.1","host":"localhost:8000","method":"OPTIONS","uri":"/.well-known/config","user_agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Safari/605.1.15","status":204,"error":"","latency":39000,"latency_human":"39µs","bytes_in":0,"bytes_out":0,"referer":"http://localhost:8888/"}
{"time":"2022-10-11T09:12:19.401008Z","time_unix":"1665479539","id":"bpZo2ms20xRoyqGREhr8WFl6p9t4MoWh","remote_ip":"172.18.0.1","host":"localhost:8000","method":"GET","uri":"/.well-known/config","user_agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Safari/605.1.15","status":200,"error":"","latency":62000,"latency_human":"62µs","bytes_in":0,"bytes_out":55,"referer":"http://localhost:8888/"}
{"time":"2022-10-11T09:12:19.479697Z","time_unix":"1665479539","id":"JsnMq0eNdJ5e1HWVfXZjYTrdzuYgiajt","remote_ip":"172.18.0.1","host":"localhost:8000","method":"OPTIONS","uri":"/me","user_agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Safari/605.1.15","status":204,"error":"","latency":97000,"latency_human":"97µs","bytes_in":0,"bytes_out":0,"referer":"http://localhost:8888/"}
{"time":"2022-10-11T09:12:19.490667Z","time_unix":"1665479539","id":"TVN0HZ21tBMMEtwOxzGu57cTu4cthzs6","remote_ip":"172.18.0.1","host":"localhost:8000","method":"GET","uri":"/me","user_agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Safari/605.1.15","status":400,"error":"code=400, message=missing or malformed jwt","latency":61000,"latency_human":"61µs","bytes_in":0,"bytes_out":50,"referer":"http://localhost:8888/"}
{"time":"2022-10-11T09:18:12.984728Z","time_unix":"1665479892","id":"qotGQr63kk9yT3FNlpdPKx9Mho1WrFMB","remote_ip":"172.18.0.1","host":"localhost:8000","method":"OPTIONS","uri":"/.well-known/config","user_agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Safari/605.1.15","status":204,"error":"","latency":150000,"latency_human":"150µs","bytes_in":0,"bytes_out":0,"referer":"http://localhost:8888/"}
{"time":"2022-10-11T09:18:12.99332Z","time_unix":"1665479892","id":"T6zo55nxiMSycU6bgv9E76ZjR3RCRHbd","remote_ip":"172.18.0.1","host":"localhost:8000","method":"GET","uri":"/.well-known/config","user_agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Safari/605.1.15","status":200,"error":"","latency":82000,"latency_human":"82µs","bytes_in":0,"bytes_out":55,"referer":"http://localhost:8888/"}
{"time":"2022-10-11T09:18:13.085154Z","time_unix":"1665479893","id":"fAIbtZDAGcppa6gSlmHgeVDQgajOQmGm","remote_ip":"172.18.0.1","host":"localhost:8000","method":"OPTIONS","uri":"/me","user_agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Safari/605.1.15","status":204,"error":"","latency":362000,"latency_human":"362µs","bytes_in":0,"bytes_out":0,"referer":"http://localhost:8888/"}
{"time":"2022-10-11T09:18:13.110946Z","time_unix":"1665479893","id":"p9Ixk7xjtcz4khwliB9HXUr1PNVlCobg","remote_ip":"172.18.0.1","host":"localhost:8000","method":"GET","uri":"/me","user_agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Safari/605.1.15","status":400,"error":"code=400, message=missing or malformed jwt","latency":235000,"latency_human":"235µs","bytes_in":0,"bytes_out":50,"referer":"http://localhost:8888/"}
{"time":"2022-10-11T09:19:38.06402Z","time_unix":"1665479978","id":"60BpNIItQYdaxMwbqhcHS9vbhCyOQqb8","remote_ip":"172.18.0.1","host":"localhost:8000","method":"OPTIONS","uri":"/user","user_agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Safari/605.1.15","status":204,"error":"","latency":150000,"latency_human":"150µs","bytes_in":0,"bytes_out":0,"referer":"http://localhost:8888/"}
{"time":"2022-10-11T09:19:38.080543Z","time_unix":"1665479978","id":"BEeATG25ue5lpCSGtCWz1ExHBt5JAL12","remote_ip":"172.18.0.1","host":"localhost:8000","method":"POST","uri":"/user","user_agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Safari/605.1.15","status":200,"error":"","latency":3494000,"latency_human":"3.494ms","bytes_in":28,"bytes_out":94,"referer":"http://localhost:8888/"}
{"time":"2022-10-11T09:19:38.094206Z","time_unix":"1665479978","id":"yGZEvFr5w5PgbbvHHreWN7iqKa0VgDLR","remote_ip":"172.18.0.1","host":"localhost:8000","method":"OPTIONS","uri":"/passcode/login/initialize","user_agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Safari/605.1.15","status":204,"error":"","latency":67000,"latency_human":"67µs","bytes_in":0,"bytes_out":0,"referer":"http://localhost:8888/"}
{"time":"2022-10-11T09:19:38.476614Z","time_unix":"1665479978","id":"zHdE4h5V5niMJ7yv31riQJerDjRVPsOI","remote_ip":"172.18.0.1","host":"localhost:8000","method":"POST","uri":"/passcode/login/initialize","user_agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Safari/605.1.15","status":200,"error":"","latency":374887000,"latency_human":"374.887ms","bytes_in":50,"bytes_out":99,"referer":"http://localhost:8888/"}
{"time":"2022-10-11T09:19:54.739095Z","time_unix":"1665479994","id":"0xVRb32uFA9WhPQJdky8yZgzpTy9xIfo","remote_ip":"172.18.0.1","host":"localhost:8000","method":"OPTIONS","uri":"/passcode/login/finalize","user_agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Safari/605.1.15","status":204,"error":"","latency":73000,"latency_human":"73µs","bytes_in":0,"bytes_out":0,"referer":"http://localhost:8888/"}
{"time":"2022-10-11T09:19:55.127985Z","time_unix":"1665479995","id":"USFb5dv0Ap79nhLe7fWODcWkjSELPDDp","remote_ip":"172.18.0.1","host":"localhost:8000","method":"POST","uri":"/passcode/login/finalize","user_agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Safari/605.1.15","status":401,"error":"Unauthorized: passcode invalid","latency":374251000,"latency_human":"374.251ms","bytes_in":61,"bytes_out":38,"referer":"http://localhost:8888/"}
{"time":"2022-10-11T09:20:06.338548Z","time_unix":"1665480006","id":"uXHX9KyCglvFSA3wvYUy8QQqiQMculNS","remote_ip":"172.18.0.1","host":"localhost:8000","method":"OPTIONS","uri":"/passcode/login/finalize","user_agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Safari/605.1.15","status":204,"error":"","latency":60000,"latency_human":"60µs","bytes_in":0,"bytes_out":0,"referer":"http://localhost:8888/"}
{"time":"2022-10-11T09:20:06.742371Z","time_unix":"1665480006","id":"ULVEMsQqwQPsRmWXF4ex362DLu9aaK67","remote_ip":"172.18.0.1","host":"localhost:8000","method":"POST","uri":"/passcode/login/finalize","user_agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Safari/605.1.15","status":200,"error":"","latency":397889000,"latency_human":"397.889ms","bytes_in":61,"bytes_out":99,"referer":"http://localhost:8888/"}
{"time":"2022-10-11T09:20:06.753688Z","time_unix":"1665480006","id":"YekM7yw6HYExGAqWcAKQ8orUOFmBOHn6","remote_ip":"172.18.0.1","host":"localhost:8000","method":"OPTIONS","uri":"/me","user_agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Safari/605.1.15","status":204,"error":"","latency":59000,"latency_human":"59µs","bytes_in":0,"bytes_out":0,"referer":"http://localhost:8888/"}
{"time":"2022-10-11T09:20:06.760034Z","time_unix":"1665480006","id":"UZqIsKri1foQAwnHOHEpu61HayXpilXA","remote_ip":"172.18.0.1","host":"localhost:8000","method":"GET","uri":"/me","user_agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Safari/605.1.15","status":200,"error":"","latency":451000,"latency_human":"451µs","bytes_in":0,"bytes_out":46,"referer":"http://localhost:8888/"}
{"time":"2022-10-11T09:20:06.771208Z","time_unix":"1665480006","id":"opIZq4kVToAy1iZMyeF3miJs0IomEKt5","remote_ip":"172.18.0.1","host":"localhost:8000","method":"OPTIONS","uri":"/users/b208dbba-84a7-43cf-a3ad-3e0f4edd21a2","user_agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Safari/605.1.15","status":204,"error":"","latency":99000,"latency_human":"99µs","bytes_in":0,"bytes_out":0,"referer":"http://localhost:8888/"}
{"time":"2022-10-11T09:20:06.779951Z","time_unix":"1665480006","id":"tQE6xUkXomZ487c9DNVMEHxoQh5rZHaK","remote_ip":"172.18.0.1","host":"localhost:8000","method":"GET","uri":"/users/b208dbba-84a7-43cf-a3ad-3e0f4edd21a2","user_agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Safari/605.1.15","status":200,"error":"","latency":4035000,"latency_human":"4.035ms","bytes_in":0,"bytes_out":175,"referer":"http://localhost:8888/"}
{"time":"2022-10-11T09:20:07.347806Z","time_unix":"1665480007","id":"fbK83MWMN73N9KhZnjO4rhlqwLAvoZ18","remote_ip":"172.18.0.6","host":"hanko:8000","method":"GET","uri":"/.well-known/jwks.json","user_agent":"Go-http-client/1.1","status":200,"error":"","latency":2353000,"latency_human":"2.353ms","bytes_in":0,"bytes_out":797,"referer":""}
{"time":"2022-10-11T09:20:07.605745Z","time_unix":"1665480007","id":"oWyKe98kxhGJVABt1Vum6ccQLfREIerK","remote_ip":"172.18.0.1","host":"localhost:8000","method":"GET","uri":"/me","user_agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Safari/605.1.15","status":200,"error":"","latency":2025000,"latency_human":"2.025ms","bytes_in":0,"bytes_out":46,"referer":"http://localhost:8888/"}
{"time":"2022-10-11T09:20:07.72657Z","time_unix":"1665480007","id":"Vdn5L7dneAw3R0WcWS6rS4sePIcpGiZf","remote_ip":"172.18.0.1","host":"localhost:8000","method":"GET","uri":"/users/b208dbba-84a7-43cf-a3ad-3e0f4edd21a2","user_agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Safari/605.1.15","status":200,"error":"","latency":12150000,"latency_human":"12.15ms","bytes_in":0,"bytes_out":175,"referer":"http://localhost:8888/"}

Configuration

database:
  user: hanko
  password: hanko
  host: postgresd
  port: 5432
  dialect: postgres
passcode:
  email:
    from_address: [email protected]
  smtp:
    host: "mailslurper"
    port: "2500"
secrets:
  keys:
    - abcedfghijklmnopqrstuvwxyz
service:
  name: Hanko Authentication Service
server:
  public:
    cors:
      enabled: true
      allow_credentials: true
      allow_origins:
        - "*"
webauthn:
  relying_party:
    origin: "http://localhost:8888"
session:
  cookie:
    secure: false # is needed for safari, because safari does not store secure cookies on localhost

Hanko Version

b450fb5

OS

macOS

OS Version

Mac OS Big Sur

Environment

Docker Compose

Additional Context

No response

docker compile quickstart failing

docker-compose -f deploy/docker-compose/quickstart.yaml -p "hanko-quickstart" up --build

...


hanko-quickstart-postgresd-1      | 
hanko-quickstart-postgresd-1      | PostgreSQL Database directory appears to contain a database; Skipping initialization
hanko-quickstart-postgresd-1      | 
hanko-quickstart-example-1        | 
hanko-quickstart-example-1        |    ____    __
hanko-quickstart-example-1        |   / __/___/ /  ___
hanko-quickstart-example-1        |  / _// __/ _ \/ _ \
hanko-quickstart-example-1        | /___/\__/_//_/\___/ v4.7.2
hanko-quickstart-example-1        | High performance, minimalist Go web framework
hanko-quickstart-example-1        | https://echo.labstack.com
hanko-quickstart-example-1        | ____________________________________O/_______
hanko-quickstart-example-1        |                                     O\
hanko-quickstart-example-1        | ⇨ http server started on [::]:8080
hanko-quickstart-hankojs-1        | /docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration
hanko-quickstart-hankojs-1        | /docker-entrypoint.sh: Looking for shell scripts in /docker-entrypoint.d/
hanko-quickstart-hankojs-1        | /docker-entrypoint.sh: Launching /docker-entrypoint.d/10-listen-on-ipv6-by-default.sh
hanko-quickstart-hankojs-1        | 10-listen-on-ipv6-by-default.sh: info: Getting the checksum of /etc/nginx/conf.d/default.conf
hanko-quickstart-mailslurper-1    | time="2022-09-05T11:21:17Z" level=info msg="Starting MailSlurper Server v1.14.1" who=MailSlurper
hanko-quickstart-hankojs-1        | 10-listen-on-ipv6-by-default.sh: info: /etc/nginx/conf.d/default.conf differs from the packaged version
hanko-quickstart-hankojs-1        | /docker-entrypoint.sh: Launching /docker-entrypoint.d/20-envsubst-on-templates.sh
hanko-quickstart-mailslurper-1    | time="2022-09-05T11:21:17Z" level=info msg="Connecting to database" who=MailSlurper
hanko-quickstart-mailslurper-1    | time="2022-09-05T11:21:17Z" level=info msg="Creating database tables..." who=MailSlurper
hanko-quickstart-hankojs-1        | /docker-entrypoint.sh: Launching /docker-entrypoint.d/30-tune-worker-processes.sh
hanko-quickstart-hankojs-1        | /docker-entrypoint.sh: Configuration complete; ready for start up
hanko-quickstart-postgresd-1      | 2022-09-05 11:21:17.743 UTC [1] LOG:  starting PostgreSQL 12.12 on x86_64-pc-linux-musl, compiled by gcc (Alpine 11.2.1_git20220219) 11.2.1 20220219, 64-bit
hanko-quickstart-postgresd-1      | 2022-09-05 11:21:17.743 UTC [1] LOG:  listening on IPv4 address "0.0.0.0", port 5432
hanko-quickstart-postgresd-1      | 2022-09-05 11:21:17.743 UTC [1] LOG:  listening on IPv6 address "::", port 5432
hanko-quickstart-postgresd-1      | 2022-09-05 11:21:17.760 UTC [1] LOG:  listening on Unix socket "/var/run/postgresql/.s.PGSQL.5432"
hanko-quickstart-hankojs-1        | 2022/09/05 11:21:17 [notice] 1#1: using the "epoll" event method
hanko-quickstart-hankojs-1        | 2022/09/05 11:21:17 [notice] 1#1: nginx/1.22.0
hanko-quickstart-hankojs-1        | 2022/09/05 11:21:17 [notice] 1#1: built by gcc 11.2.1 20220219 (Alpine 11.2.1_git20220219) 
hanko-quickstart-hankojs-1        | 2022/09/05 11:21:17 [notice] 1#1: OS: Linux 5.15.37-0-virt
hanko-quickstart-hankojs-1        | 2022/09/05 11:21:17 [notice] 1#1: getrlimit(RLIMIT_NOFILE): 1048576:1048576
hanko-quickstart-hankojs-1        | 2022/09/05 11:21:17 [notice] 1#1: start worker processes
hanko-quickstart-hankojs-1        | 2022/09/05 11:21:17 [notice] 1#1: start worker process 32
hanko-quickstart-hankojs-1        | 2022/09/05 11:21:17 [notice] 1#1: start worker process 33
hanko-quickstart-mailslurper-1    | time="2022-09-05T11:21:17Z" level=info msg="Created tables successfully." who=MailSlurper
hanko-quickstart-mailslurper-1    | time="2022-09-05T11:21:17Z" level=info msg="Worker pool configured for 1000 workers" who="SMTP Server Pool"
hanko-quickstart-mailslurper-1    | ⇨ http server started on [::]:8080
hanko-quickstart-mailslurper-1    | time="2022-09-05T11:21:17Z" level=info msg="SMTP listener running on 0.0.0.0:2500" who="SMTP Listener"
hanko-quickstart-mailslurper-1    | time="2022-09-05T11:21:17Z" level=info msg="1 receiver(s) listening" who="SMTP Listener"
hanko-quickstart-mailslurper-1    | time="2022-09-05T11:21:17Z" level=info msg="HTTP admin listener running on 0.0.0.0:8080" who=MailSlurper
hanko-quickstart-mailslurper-1    | ⇨ http server started on [::]:8085
hanko-quickstart-postgresd-1      | 2022-09-05 11:21:17.892 UTC [23] LOG:  database system was shut down at 2022-09-05 11:20:59 UTC
hanko-quickstart-postgresd-1      | 2022-09-05 11:21:17.913 UTC [1] LOG:  database system is ready to accept connections
hanko-quickstart-hanko-migrate-1  | 2022/09/05 11:21:28 Using config file: /etc/config/config.yaml
hanko-quickstart-hanko-migrate-1  | 2022/09/05 11:21:28 migrate up
hanko-quickstart-hanko-migrate-1  | [POP] 2022/09/05 11:21:28 info - Migrations already up to date, nothing to apply
hanko-quickstart-hanko-migrate-1  | [POP] 2022/09/05 11:21:28 info - 0.2065 seconds
hanko-quickstart-hanko-migrate-1 exited with code 0
hanko-quickstart-hanko-1          | 2022/09/05 11:21:29 Using config file: /etc/config/config.yaml
hanko-quickstart-hanko-1          | ⇨ http server started on [::]:8001
hanko-quickstart-hanko-1          | ⇨ http server started on [::]:8000
hanko-quickstart-hanko-1          | {"time":"2022-09-05T11:21:37.976606509Z","time_unix":"1662376897","id":"qhPXhioIX0yCpH95nPcXKCWRKdNTirqR","remote_ip":"172.22.0.1","host":"localhost:8000","method":"GET","uri":"/","user_agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36","status":404,"error":"code=404, message=Not Found","latency":659000,"latency_human":"659µs","bytes_in":0,"bytes_out":35,"referer":""}
hanko-quickstart-hanko-1          | {"time":"2022-09-05T11:21:39.820462509Z","time_unix":"1662376899","id":"4IKln0XfdJ8cgY0glZAYAfZQInC1Fxkn","remote_ip":"172.22.0.1","host":"localhost:8000","method":"GET","uri":"/","user_agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36","status":404,"error":"code=404, message=Not Found","latency":94000,"latency_human":"94µs","bytes_in":0,"bytes_out":35,"referer":""}
hanko-quickstart-hanko-1          | {"time":"2022-09-05T11:21:52.372038509Z","time_unix":"1662376912","id":"xxPeBwdrO2tsGpn2UK9hKzblzq7ONeLf","remote_ip":"172.22.0.1","host":"localhost:8001","method":"GET","uri":"/","user_agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36","status":404,"error":"code=404, message=Not Found","latency":3302000,"latency_human":"3.302ms","bytes_in":0,"bytes_out":24,"referer":""}
hanko-quickstart-hanko-1          | {"time":"2022-09-05T11:21:52.912402509Z","time_unix":"1662376912","id":"XfMZwmQ3Q8H2N1WTcXfOjO7W0BwRAC5n","remote_ip":"172.22.0.1","host":"localhost:8001","method":"GET","uri":"/favicon.ico","user_agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36","status":404,"error":"code=404, message=Not Found","latency":65000,"latency_human":"65µs","bytes_in":0,"bytes_out":24,"referer":"http://localhost:8001/"}
hanko-quickstart-hanko-1          | {"time":"2022-09-05T11:23:20.835662509Z","time_unix":"1662377000","id":"hdX0inp1vFCjzVAADVrbIhqM3EWVZfGx","remote_ip":"172.22.0.1","host":"localhost:8000","method":"GET","uri":"/","user_agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36","status":404,"error":"code=404, message=Not Found","latency":88000,"latency_human":"88µs","bytes_in":0,"bytes_out":35,"referer":""}

Broswer:

http://localhost:8000/
{"code":404,"message":"Not Found"}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.