Skip to content

Commit

Permalink
Update using custom domain minus one as cookie domain
Browse files Browse the repository at this point in the history
ref #1393
  • Loading branch information
louischan-oursky committed Aug 27, 2021
2 parents 115c7ba + deecf1d commit 526903c
Show file tree
Hide file tree
Showing 22 changed files with 268 additions and 104 deletions.
5 changes: 5 additions & 0 deletions cmd/portal/cobraviper.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,3 +64,8 @@ var ArgPlanNameForAppUpdate = &cobraviper.StringArgument{
Usage: "Plan name",
DefaultValue: "custom",
}

var ArgAppHostSuffix = &cobraviper.StringArgument{
ArgumentName: "app-host-suffix",
Usage: "App host suffix",
}
119 changes: 119 additions & 0 deletions cmd/portal/migrate_resources_migrate_cookie_domain.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
package main

import (
"encoding/base64"
"fmt"
"log"
"net/url"
"strings"

"github.com/spf13/cobra"
"sigs.k8s.io/yaml"

"github.com/authgear/authgear-server/cmd/portal/internal"
"github.com/authgear/authgear-server/pkg/util/httputil"
)

var migrateCookieDomainAppHostSuffix string

var cmdInternalMigrateCookieDomain = &cobra.Command{
Use: "migrate-cookie-domain",
Short: "Set cookie domain for apps which are using custom domain",
RunE: func(cmd *cobra.Command, args []string) error {
binder := getBinder()

dbURL, err := binder.GetRequiredString(cmd, ArgDatabaseURL)
if err != nil {
return err
}

dbSchema, err := binder.GetRequiredString(cmd, ArgDatabaseSchema)
if err != nil {
return err
}

migrateCookieDomainAppHostSuffix, err = binder.GetRequiredString(cmd, ArgAppHostSuffix)
if err != nil {
return err
}

internal.MigrateResources(&internal.MigrateResourcesOptions{
DatabaseURL: dbURL,
DatabaseSchema: dbSchema,
UpdateConfigSourceFunc: migrateCookieDomain,
DryRun: &MigrateResourcesDryRun,
})

return nil
},
}

func migrateCookieDomain(appID string, configSourceData map[string]string, dryRun bool) error {
encodedData := configSourceData["authgear.yaml"]
decoded, err := base64.StdEncoding.DecodeString(encodedData)
if err != nil {
return fmt.Errorf("failed decode authgear.yaml: %w", err)
}

if dryRun {
log.Printf("Converting app (%s)", appID)
log.Printf("Before updated:")
log.Printf("\n%s\n", string(decoded))
}

m := make(map[string]interface{})
err = yaml.Unmarshal(decoded, &m)
if err != nil {
return fmt.Errorf("failed unmarshal yaml: %w", err)
}

httpConfig, ok := m["http"].(map[string]interface{})
if !ok {
return nil
}

publicOrigin, ok := httpConfig["public_origin"].(string)
if !ok {
return fmt.Errorf("cannot read public origin from authgear.yaml: %s", appID)
}

if strings.HasSuffix(publicOrigin, migrateCookieDomainAppHostSuffix) {
// skip default domain
log.Printf("skip default domain...")
return nil
}

_, ok = httpConfig["cookie_domain"].(string)
if ok {
// skip the config that has cookie_domain
log.Printf("skip config that has cookie_domain...")
return nil
}

u, err := url.Parse(publicOrigin)
if err != nil {
return fmt.Errorf("failed to parse public origin: %w", err)
}

cookieDomain := httputil.CookieDomainWithoutPort(u.Host)
httpConfig["cookie_domain"] = cookieDomain

migrated, err := yaml.Marshal(m)
if err != nil {
return fmt.Errorf("failed marshal yaml: %w", err)
}

if dryRun {
log.Printf("After updated:")
log.Printf("\n%s\n", string(migrated))
}

configSourceData["authgear.yaml"] = base64.StdEncoding.EncodeToString(migrated)
return nil
}

func init() {
binder := getBinder()
cmdInternalBreakingChangeMigrateResources.AddCommand(cmdInternalMigrateCookieDomain)
binder.BindString(cmdInternalMigrateCookieDomain.Flags(), ArgAppHostSuffix)
}
1 change: 1 addition & 0 deletions pkg/portal/graphql/domain.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ var domain = graphql.NewObject(graphql.ObjectConfig{
"id": &graphql.Field{Type: graphql.NewNonNull(graphql.String)},
"createdAt": &graphql.Field{Type: graphql.NewNonNull(graphql.DateTime)},
"domain": &graphql.Field{Type: graphql.NewNonNull(graphql.String)},
"cookieDomain": &graphql.Field{Type: graphql.NewNonNull(graphql.String)},
"apexDomain": &graphql.Field{Type: graphql.NewNonNull(graphql.String)},
"verificationDNSRecord": &graphql.Field{Type: graphql.NewNonNull(graphql.String)},
"isCustom": &graphql.Field{Type: graphql.NewNonNull(graphql.Boolean)},
Expand Down
1 change: 1 addition & 0 deletions pkg/portal/model/domain.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ type Domain struct {
AppID string `json:"appID"`
CreatedAt time.Time `json:"createdAt"`
Domain string `json:"domain"`
CookieDomain string `json:"cookieDomain"`
ApexDomain string `json:"apexDomain"`
VerificationDNSRecord string `json:"verificationDNSRecord"`
IsCustom bool `json:"isCustom"`
Expand Down
10 changes: 10 additions & 0 deletions pkg/portal/service/domain.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"github.com/authgear/authgear-server/pkg/lib/infra/db/globaldb"
"github.com/authgear/authgear-server/pkg/portal/model"
"github.com/authgear/authgear-server/pkg/util/clock"
"github.com/authgear/authgear-server/pkg/util/httputil"
corerand "github.com/authgear/authgear-server/pkg/util/rand"
"github.com/authgear/authgear-server/pkg/util/uuid"
)
Expand Down Expand Up @@ -408,12 +409,21 @@ func (d *domain) toModel(isVerified bool) *model.Domain {
prefix = "pending:"
}

// for default domain, original domain will be used for cookie domain
// for custom domain, cookie domain is derived from the
// CookieDomainWithoutPort function
cookieDomain := d.Domain
if d.IsCustom {
cookieDomain = httputil.CookieDomainWithoutPort(d.Domain)
}

return &model.Domain{
// Base64-encoded to avoid invalid k8s resource label invalid chars
ID: base64.RawURLEncoding.EncodeToString([]byte(prefix + d.ID)),
AppID: d.AppID,
CreatedAt: d.CreatedAt,
Domain: d.Domain,
CookieDomain: cookieDomain,
ApexDomain: d.ApexDomain,
VerificationDNSRecord: domainVerificationDNSRecord(d.VerificationNonce),
IsCustom: d.IsCustom,
Expand Down
17 changes: 12 additions & 5 deletions pkg/util/httputil/cookie.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,12 @@ func UpdateCookie(w http.ResponseWriter, cookie *http.Cookie) {
header["Set-Cookie"] = setCookies
}

// CookieDomainFromETLDPlusOneWithoutPort derives host from r.
// CookieDomainWithoutPort derives host from r.
// If host has port, the port is removed.
// If host-1 is longer than ETLD+1, host-1 is returned.
// If ETLD+1 cannot be derived, an empty string is returned.
// The return value never have port.
func CookieDomainFromETLDPlusOneWithoutPort(host string) string {
func CookieDomainWithoutPort(host string) string {
// Trim the port if it is present.
// We have to trim the port first.
// Passing host:port to EffectiveTLDPlusOne confuses it.
Expand All @@ -64,12 +65,18 @@ func CookieDomainFromETLDPlusOneWithoutPort(host string) string {
return ""
}

host, err := publicsuffix.EffectiveTLDPlusOne(host)
eTLDPlusOne, err := publicsuffix.EffectiveTLDPlusOne(host)
if err != nil {
return ""
}

return host
// host has a valid ETLDPlusOne, it's safe to split the domain with .
hostMinusOne := host[1+strings.Index(host, "."):]
if len(hostMinusOne) > len(eTLDPlusOne) {
return hostMinusOne
}

return eTLDPlusOne
}

type CookieManager struct {
Expand All @@ -85,7 +92,7 @@ func (f *CookieManager) fixupCookie(cookie *http.Cookie) {

cookie.Secure = proto == "https"
if cookie.Domain == "" {
cookie.Domain = CookieDomainFromETLDPlusOneWithoutPort(host)
cookie.Domain = CookieDomainWithoutPort(host)
}

if cookie.SameSite == http.SameSiteNoneMode &&
Expand Down
10 changes: 7 additions & 3 deletions pkg/util/httputil/cookie_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,10 +106,10 @@ func TestUpdateCookie(t *testing.T) {
})
}

func TestCookieDomainFromETLDPlusOneWithoutPort(t *testing.T) {
Convey("CookieDomainFromETLDPlusOneWithoutPort", t, func() {
func TestCookieDomainWithoutPort(t *testing.T) {
Convey("CookieDomainWithoutPort", t, func() {
check := func(in string, out string) {
actual := httputil.CookieDomainFromETLDPlusOneWithoutPort(in)
actual := httputil.CookieDomainWithoutPort(in)
So(out, ShouldEqual, actual)
}
check("localhost", "")
Expand Down Expand Up @@ -139,5 +139,9 @@ func TestCookieDomainFromETLDPlusOneWithoutPort(t *testing.T) {
check("www.example.co.jp", "example.co.jp")
check("www.example.co.jp:80", "example.co.jp")
check("www.example.co.jp:8080", "example.co.jp")

check("auth.app.example.co.jp", "app.example.co.jp")
check("auth.app.example.co.jp:80", "app.example.co.jp")
check("auth.app.example.co.jp:8080", "app.example.co.jp")
})
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ const COPY_ICON_STLYES: IButtonStyles = {

interface FormState {
publicOrigin: string;
cookieDomain?: string;
clients: OAuthClientConfig[];
allowedOrigins: string[];
persistentCookie: boolean;
Expand All @@ -58,6 +59,7 @@ interface FormState {
function constructFormState(config: PortalAPIAppConfig): FormState {
return {
publicOrigin: config.http?.public_origin ?? "",
cookieDomain: config.http?.cookie_domain,
clients: config.oauth?.clients ?? [],
allowedOrigins: config.http?.allowed_origins ?? [],
persistentCookie: !(config.session?.cookie_non_persistent ?? false),
Expand Down Expand Up @@ -284,7 +286,8 @@ const SessionConfigurationWidget: React.FC<SessionConfigurationWidgetProps> =
<FormattedMessage
id="SessionConfigurationWidget.description"
values={{
endpoint: state.publicOrigin,
// cookieDomain wil be empty only if authgear.yaml is updated manually
domain: state.cookieDomain ?? state.publicOrigin,
}}
/>
</WidgetDescription>
Expand Down
13 changes: 8 additions & 5 deletions portal/src/graphql/portal/CustomDomainListScreen.module.scss
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
.content {
margin: 0 32px;
.root {
display: flex;
flex-direction: column;
}

.verifySuccessMessageBar {
Expand All @@ -10,12 +11,14 @@
}
}

.widget {
margin: 10px 15px;
max-width: 750px;
}

.description {
display: block;
margin: 16px 8px;
width: 650px;
font-size: 13px;
line-height: 1.3;
}

.domainListColumn {
Expand Down
Loading

0 comments on commit 526903c

Please sign in to comment.