Skip to content

Commit

Permalink
Merge branch 'main' into feat-extend-user-import
Browse files Browse the repository at this point in the history
  • Loading branch information
FreddyDevelop authored Dec 5, 2024
2 parents c776fb8 + 21fd1d4 commit a65f62f
Show file tree
Hide file tree
Showing 97 changed files with 3,325 additions and 204 deletions.
38 changes: 31 additions & 7 deletions backend/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@ easily integrated into any web app with as little as two lines of code.
- [Cross-domain communication](#cross-domain-communication)
- [Audit logs](#audit-logs)
- [Rate Limiting](#rate-limiting)
- [Social logins](#social-logins)
- [Social connections](#social-connections)
- [Built-in providers](#built-in-providers)
- [Custom OAuth/OIDC providers](#custom-oauthoidc-providers)
- [Account linking](#account-linking)
- [User import](#user-import)
- [Webhooks](#webhooks)
- [API specification](#api-specification)
Expand Down Expand Up @@ -432,13 +435,22 @@ It uses a combination of user-id/IP to mitigate DoS attacks on user accounts. Yo
In production systems, you may want to hide the
Hanko service behind a proxy or gateway (e.g. Kong, Traefik) to provide additional network-based rate limiting.

### Social Logins
### Social connections

Hanko supports OAuth-based ([authorization code flow](https://www.rfc-editor.org/rfc/rfc6749#section-1.3.1)) third
party provider logins. See the `third_party` option in
the [configuration reference](https://github.com/teamhanko/hanko/wiki/hanko-properties-third_party) on how to
configure them. All provider configurations require provider credentials. See the guides in the official
documentation for instructions on how to obtain these:
party provider logins. The `third_party` configuration
[option](https://github.com/teamhanko/hanko/wiki/config-properties-third_party) contains all relevant configuration.
This includes options for setting up redirect URLs (in case of success or error on authentication with a provider) that
apply to both [built-in](#built-in-providers) and
[custom](#custom-oauthoidc-providers) providers.


#### Built-in providers

Built-in providers can be configured through the `third_party.providers` configuration [option](https://github.com/teamhanko/hanko/wiki/config-properties-third_party).
They must be explicitly `enabled` (i.e. providers are disabled default).
All provider configurations require provider credentials in the form of a client ID (`client_id`)
and a client secret (`secret`). See the guides in the official documentation for instructions on how to obtain these:

- [Apple](https://docs.hanko.io/guides/authentication-methods/oauth/apple)
- [Discord](https://docs.hanko.io/guides/authentication-methods/oauth/discord)
Expand All @@ -447,9 +459,21 @@ documentation for instructions on how to obtain these:
- [LinkedIn](https://docs.hanko.io/guides/authentication-methods/oauth/linkedin)
- [Microsoft](https://docs.hanko.io/guides/authentication-methods/oauth/microsoft)

#### Custom OAuth/OIDC providers

Custom providers can be configured through the `third_party.custom_providers` configuration
[option](https://github.com/teamhanko/hanko/wiki/config-properties-third_party-properties-custom_providers).
Like built-in providers they must be explicitly `enabled` and require a `client_id` and `secret`, which must
be obtained from the respective provider.
Custom providers can use either OAuth or OIDC. OIDC providers can be configured to use
[OIDC Discovery](https://openid.net/specs/openid-connect-discovery-1_0.html) by setting the `use_discovery`
option to `true`. An `issuer` must be configured too in that case. Otherwise both OAuth and OIDC providers
can manually define required endpoints (`authorization_endpoint`, `token_endpoint`, `userinfo_endpoint`).
`scopes` must be explicitly defined (with `openid` being the minimum requirement in case of OIDC providers).

#### Account linking

The `allow_linking` configuration option for providers determines whether automatic account linking for this provider
The `allow_linking` configuration option for built-in and custom providers determines whether automatic account linking for this provider
is activated. Note that account linking is based on e-mail addresses and OAuth providers may allow account holders to
use unverified e-mail addresses or may not provide any information at all about the verification status of e-mail
addresses. This poses a security risk and potentially allows bad actors to hijack existing Hanko
Expand Down
11 changes: 11 additions & 0 deletions backend/config/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ log:
mfa:
acquire_on_login: false
acquire_on_registration: true
device_trust_cookie_name: hanko-device-token
device_trust_duration: 720h
device_trust_policy: prompt
enabled: true
optional: true
security_keys:
Expand Down Expand Up @@ -89,6 +92,14 @@ service:
session:
lifespan: 12h
enable_auth_token_header: false
server_side:
enabled: false
limit: 100
cookie:
http_only: true
retention: persistent
same_site: strict
secure: true
third_party:
providers:
apple:
Expand Down
40 changes: 29 additions & 11 deletions backend/config/config_default.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,10 @@ func DefaultConfig() *Config {
Session: Session{
Lifespan: "12h",
Cookie: Cookie{
HttpOnly: true,
SameSite: "strict",
Secure: true,
HttpOnly: true,
Retention: "persistent",
SameSite: "strict",
Secure: true,
},
ServerSide: ServerSide{
Enabled: false,
Expand Down Expand Up @@ -117,21 +118,35 @@ func DefaultConfig() *Config {
},
ThirdParty: ThirdParty{
Providers: ThirdPartyProviders{
Google: ThirdPartyProvider{
DisplayName: "Google",
AllowLinking: true,
},
GitHub: ThirdPartyProvider{
DisplayName: "GitHub",
AllowLinking: true,
},
Apple: ThirdPartyProvider{
DisplayName: "Apple",
AllowLinking: true,
Name: "apple",
},
Discord: ThirdPartyProvider{
DisplayName: "Discord",
AllowLinking: true,
Name: "discord",
},
LinkedIn: ThirdPartyProvider{
DisplayName: "LinkedIn",
AllowLinking: true,
Name: "linkedin",
},
Microsoft: ThirdPartyProvider{
DisplayName: "Microsoft",
AllowLinking: true,
Name: "microsoft",
},
GitHub: ThirdPartyProvider{
DisplayName: "GitHub",
AllowLinking: true,
Name: "github",
},
Google: ThirdPartyProvider{
DisplayName: "Google",
AllowLinking: true,
Name: "google",
},
},
},
Expand Down Expand Up @@ -168,6 +183,9 @@ func DefaultConfig() *Config {
MFA: MFA{
AcquireOnLogin: false,
AcquireOnRegistration: true,
DeviceTrustCookieName: "hanko-device-token",
DeviceTrustDuration: 30 * 24 * time.Hour, // 30 days
DeviceTrustPolicy: "prompt",
Enabled: true,
Optional: true,
SecurityKeys: SecurityKeys{
Expand Down
22 changes: 22 additions & 0 deletions backend/config/config_mfa.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
package config

import (
"github.com/invopop/jsonschema"
"time"
)

type SecurityKeys struct {
// `attestation_preference` is used to specify the preference regarding attestation conveyance during
// credential generation.
Expand Down Expand Up @@ -28,6 +33,14 @@ type MFA struct {
AcquireOnLogin bool `yaml:"acquire_on_login" json:"acquire_on_login" koanf:"acquire_on_login" jsonschema:"default=false"`
// `acquire_on_registration` configures if users are prompted creating an MFA credential on registration.
AcquireOnRegistration bool `yaml:"acquire_on_registration" json:"acquire_on_registration" koanf:"acquire_on_registration" jsonschema:"default=true"`
// `device_trust_cookie_name` is the name of the cookie used to store the token of a trusted device.
DeviceTrustCookieName string `yaml:"device_trust_cookie_name" json:"device_trust_cookie_name,omitempty" koanf:"device_trust_cookie_name" jsonschema:"default=hanko_device_token"`
// `device_trust_duration` configures the duration a device remains trusted after authentication; once expired, the
// user must reauthenticate with MFA.
DeviceTrustDuration time.Duration `yaml:"device_trust_duration" json:"device_trust_duration" koanf:"device_trust_duration" jsonschema:"default=720h,type=string"`
// `device_trust_policy` determines the conditions under which a device or browser is considered trusted, allowing
// MFA to be skipped for subsequent logins.
DeviceTrustPolicy string `yaml:"device_trust_policy" json:"device_trust_policy,omitempty" koanf:"device_trust_policy" split_words:"true" jsonschema:"default=prompt,enum=always,enum=prompt,enum=never"`
// `enabled` determines whether multi-factor-authentication is enabled.
Enabled bool `yaml:"enabled" json:"enabled" koanf:"enabled" jsonschema:"default=true"`
// `optional` determines whether users must create an MFA credential when prompted. The MFA credential cannot be
Expand All @@ -38,3 +51,12 @@ type MFA struct {
// `totp` configures the TOTP (Time-Based One-Time-Password) method for multi-factor-authentication.
TOTP TOTP `yaml:"totp" json:"totp,omitempty" koanf:"totp" jsonschema:"title=totp"`
}

func (MFA) JSONSchemaExtend(schema *jsonschema.Schema) {
deviceTrustPolicy, _ := schema.Properties.Get("device_trust_policy")
deviceTrustPolicy.Extras = map[string]any{"meta:enum": map[string]string{
"always": "Devices are trusted without user consent until the trust expires, so MFA is skipped during subsequent logins.",
"prompt": "The user can choose to trust the current device to skip MFA for subsequent logins.",
"never": "Devices are considered untrusted, so MFA is required for each login.",
}}
}
12 changes: 12 additions & 0 deletions backend/config/config_session.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package config

import (
"errors"
"github.com/invopop/jsonschema"
"time"
)

Expand Down Expand Up @@ -44,6 +45,8 @@ type Cookie struct {
HttpOnly bool `yaml:"http_only" json:"http_only,omitempty" koanf:"http_only" split_words:"true" jsonschema:"default=true"`
// `name` is the name of the cookie.
Name string `yaml:"name" json:"name,omitempty" koanf:"name" jsonschema:"default=hanko"`
// `retention` determines the retention behavior of authentication cookies.
Retention string `yaml:"retention" json:"retention,omitempty" koanf:"retention" split_words:"true" jsonschema:"default=persistent,enum=session,enum=persistent,enum=prompt"`
// `same_site` controls whether a cookie is sent with cross-site requests.
// See [here](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#samesitesamesite-value) for
// more details.
Expand All @@ -56,6 +59,15 @@ type Cookie struct {
Secure bool `yaml:"secure" json:"secure,omitempty" koanf:"secure" jsonschema:"default=true"`
}

func (Cookie) JSONSchemaExtend(schema *jsonschema.Schema) {
retention, _ := schema.Properties.Get("retention")
retention.Extras = map[string]any{"meta:enum": map[string]string{
"session": "Issues a temporary cookie that lasts for the duration of the browser session.",
"persistent": "Issues a cookie that remains stored on the user's device until it reaches its expiration date.",
"prompt": "Allows the user to choose whether to stay signed in. If the user selects 'Stay signed in', a persistent cookie is issued; a session cookie otherwise.",
}}
}

func (c *Cookie) GetName() string {
if c.Name != "" {
return c.Name
Expand Down
Loading

0 comments on commit a65f62f

Please sign in to comment.