Skip to content

Commit

Permalink
Add CookieSameSite DSL (#3401)
Browse files Browse the repository at this point in the history
* Add CookieSameSite DSL

* Add missing codegen

* Fix codegen to use http.SameSite

* Follow redeclaration convention

* Embed http.SameSite enum directly

---------

Co-authored-by: Raphael Simon <simon.raphael@gmail.com>
  • Loading branch information
jaredLunde and raphael authored Oct 23, 2023
1 parent 8af299c commit 88ce4e6
Show file tree
Hide file tree
Showing 7 changed files with 85 additions and 0 deletions.
35 changes: 35 additions & 0 deletions dsl/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,13 @@ const (
StatusNetworkAuthenticationRequired = expr.StatusNetworkAuthenticationRequired
)

const (
CookieSameSiteStrict = expr.CookieSameSiteStrict
CookieSameSiteLax = expr.CookieSameSiteLax
CookieSameSiteNone = expr.CookieSameSiteNone
CookieSameSiteDefault = expr.CookieSameSiteDefault
)

// HTTP defines the HTTP transport specific properties of an API, a service or a
// single method. The function maps the method payload and result types to HTTP
// properties such as parameters (via path wildcards or query strings), request
Expand Down Expand Up @@ -591,6 +598,34 @@ func CookieHTTPOnly() {
cookieAttribute("http-only", "HttpOnly")
}

// CookieSameSite initializes the "same-site" attribute of a HTTP response
// cookie with "CookieSameSiteStrict", "CookieSameSiteLax", "CookieSameSiteNone",
// or "CookieSameSiteDefault".
//
// CookieSameSite must appear in a Cookie expression.
//
// Example:
//
// var _ = Service("account", func() {
// Method("create", func() {
// Result(Account)
// HTTP(func() {
// Response(StatusCreated, func() {
// Cookie("session:SID", String)
// CookieSameSite(CookieSameSiteStrict)
// })
// })
// })
// })
func CookieSameSite(s expr.CookieSameSiteValue) {
_, ok := eval.Current().(*expr.HTTPResponseExpr)
if !ok {
eval.IncompatibleDSL()
return
}
cookieAttribute("same-site", string(s))
}

// Params groups a set of Param expressions. It makes it possible to list
// required parameters using the Required function.
//
Expand Down
11 changes: 11 additions & 0 deletions expr/attribute.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,10 @@ type (
// ValidationFormat is the type used to enumerate the possible string
// formats.
ValidationFormat string

// CookieSameSiteValue is the type used to enumerate the possible cookie
// SameSite values.
CookieSameSiteValue string
)

const (
Expand Down Expand Up @@ -148,6 +152,13 @@ const (
FormatRFC1123 = "rfc1123"
)

const (
CookieSameSiteStrict CookieSameSiteValue = "strict"
CookieSameSiteLax CookieSameSiteValue = "lax"
CookieSameSiteNone CookieSameSiteValue = "none"
CookieSameSiteDefault CookieSameSiteValue = "default"
)

// EvalName returns the name used by the DSL evaluation.
func (*AttributeExpr) EvalName() string {
return "attribute"
Expand Down
1 change: 1 addition & 0 deletions expr/http_cookie_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ func TestHTTPResponseCookie(t *testing.T) {
{"path", testdata.CookiePathDSL, Props{"cookie:path": testdata.CookiePathValue}},
{"secure", testdata.CookieSecureDSL, Props{"cookie:secure": "Secure"}},
{"http-only", testdata.CookieHTTPOnlyDSL, Props{"cookie:http-only": "HttpOnly"}},
{"same-site", testdata.CookieSameSiteDSL, Props{"cookie:same-site": testdata.CookieSameSiteValue}},
}
for _, c := range cases {
t.Run(c.Name, func(t *testing.T) {
Expand Down
19 changes: 19 additions & 0 deletions expr/testdata/cookie_dsls.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,3 +124,22 @@ var CookieHTTPOnlyDSL = func() {
})
})
}

const CookieSameSiteValue = CookieSameSiteStrict

var CookieSameSiteDSL = func() {
Service("CookieSvc", func() {
Method("Method", func() {
Result(func() {
Attribute("cookie", String)
})
HTTP(func() {
POST("/")
Response(StatusOK, func() {
Cookie("cookie")
CookieSameSite(CookieSameSiteValue)
})
})
})
})
}
3 changes: 3 additions & 0 deletions http/codegen/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -486,6 +486,9 @@ func {{ .RequestEncoder }}(encoder func(*http.Request) goahttp.Encoder) func(*ht
{{- if .HTTPOnly }}
HttpOnly: true,
{{- end }}
{{- if .SameSite }}
SameSite: {{ .SameSite }},
{{- end }}
})
}
{{- end }}
Expand Down
3 changes: 3 additions & 0 deletions http/codegen/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -1354,6 +1354,9 @@ const responseT = `{{ define "response" -}}
{{- if .HTTPOnly }}
HttpOnly: true,
{{- end }}
{{- if .SameSite }}
SameSite: {{ .SameSite }},
{{- end }}
})
{{- if or $checkNil $initDef }}
}
Expand Down
13 changes: 13 additions & 0 deletions http/codegen/service_data.go
Original file line number Diff line number Diff line change
Expand Up @@ -506,6 +506,8 @@ type (
Secure bool
// HTTPOnly sets the cookie "http-only" attribute to "HttpOnly" if true.
HTTPOnly bool
// SameSite sets the cookie "same-site" attribute to the given value.
SameSite string
}

// TypeData contains the data needed to render a type definition.
Expand Down Expand Up @@ -2534,6 +2536,17 @@ func extractCookies(a *expr.MappedAttributeExpr, svcAtt *expr.AttributeExpr, svc
c.Secure = v[0] == "Secure"
case "cookie:http-only":
c.HTTPOnly = v[0] == "HttpOnly"
case "cookie:same-site":
switch v[0] {
case string(expr.CookieSameSiteLax):
c.SameSite = "http.SameSiteLaxMode"
case string(expr.CookieSameSiteStrict):
c.SameSite = "http.SameSiteStrictMode"
case string(expr.CookieSameSiteNone):
c.SameSite = "http.SameSiteNoneMode"
case string(expr.CookieSameSiteDefault):
c.SameSite = "http.SameSiteDefaultMode"
}
}
}
cookies = append(cookies, c)
Expand Down

0 comments on commit 88ce4e6

Please sign in to comment.