Skip to content

Commit 4db14ec

Browse files
authored
Add OIDC backchannel logout (#4513)
2 parents e4a8126 + 0b1fa89 commit 4db14ec

File tree

13 files changed

+160
-20
lines changed

13 files changed

+160
-20
lines changed

docs/delegated-auth.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,24 @@ Set-Cookie: ...
177177
Location: https://name00001-home.mycozy.cloud/
178178
```
179179

180+
#### POST /oidc/:context/logout
181+
182+
This route implements the OpenID Connect Back-Channel Logout. It means that the
183+
SSO can call this endpoint to logout the user.
184+
185+
```http
186+
POST /oidc/a-context/logout HTTP/1.1
187+
Host: name00001.mycozy.cloud
188+
Content-Type: application/x-www-form-urlencoded
189+
190+
logout_token=eyJhbGci ... .eyJpc3Mi ... .T3BlbklE ...
191+
```
192+
193+
```http
194+
HTTP/1.1 200 OK
195+
Cache-Control: no-store
196+
```
197+
180198
#### POST /oidc/access_token
181199

182200
This additional route can be used by an OAuth client (like a mobile app) when

model/session/session.go

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ type Session struct {
4141
LastSeen time.Time `json:"last_seen"`
4242
LongRun bool `json:"long_run"`
4343
ShortRun bool `json:"short_run"`
44+
SID string `json:"sid,omitempty"` // only present with OIDC
4445
}
4546

4647
// DocType implements couchdb.Doc
@@ -101,14 +102,15 @@ func (s *Session) OlderThan(t time.Duration) bool {
101102
}
102103

103104
// New creates a session in couchdb for the given instance
104-
func New(i *instance.Instance, duration Duration) (*Session, error) {
105+
func New(i *instance.Instance, duration Duration, sid string) (*Session, error) {
105106
now := time.Now()
106107
s := &Session{
107108
instance: i,
108109
LastSeen: now,
109110
CreatedAt: now,
110111
ShortRun: duration == ShortRun,
111112
LongRun: duration == LongRun,
113+
SID: sid,
112114
}
113115
if err := couchdb.CreateDoc(i, s); err != nil {
114116
return nil, err
@@ -302,6 +304,21 @@ func DeleteOthers(i *instance.Instance, selfSessionID string) error {
302304
return nil
303305
}
304306

307+
// DeleteBySID is used for the OIDC back-channel logout. It deletes the sessions
308+
// for the current device of the user.
309+
func DeleteBySID(inst *instance.Instance, sid string) error {
310+
return couchdb.ForeachDocs(inst, consts.Sessions, func(_ string, data json.RawMessage) error {
311+
var s Session
312+
if err := json.Unmarshal(data, &s); err != nil {
313+
return err
314+
}
315+
if s.SID == sid {
316+
s.Delete(inst)
317+
}
318+
return nil
319+
})
320+
}
321+
305322
// cookieSessionMACConfig returns the options to authenticate the session
306323
// cookie.
307324
//

pkg/couchdb/index.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,7 @@ func IndexesByDoctype(doctype string) []*mango.Index {
317317
// properly.
318318
var globalIndexes = []*mango.Index{
319319
mango.MakeIndex(consts.Exports, "by-domain", mango.IndexDef{Fields: []string{"domain", "created_at"}}),
320+
mango.MakeIndex(consts.Instances, "by-oidcid", mango.IndexDef{Fields: []string{"oidc_id"}}),
320321
}
321322

322323
// secretIndexes is the index list required on the secret databases to run

web/accounts/oauth.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -344,7 +344,7 @@ func checkLogin(next echo.HandlerFunc) echo.HandlerFunc {
344344
}
345345

346346
if !wasLoggedIn {
347-
sessionID, err := auth.SetCookieForNewSession(c, session.ShortRun)
347+
sessionID, err := auth.SetCookieForNewSession(c, session.ShortRun, "")
348348
req := c.Request()
349349
if err == nil {
350350
if err = session.StoreNewLoginEntry(inst, sessionID, "", req, "session_code", false); err != nil {

web/accounts/oauth_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ func TestOauth(t *testing.T) {
3737
setup := testutils.NewSetup(t, t.Name())
3838
ts := setup.GetTestServer("/accounts", Routes, func(r *echo.Echo) *echo.Echo {
3939
r.POST("/login", func(c echo.Context) error {
40-
sess, _ := session.New(testInstance, session.LongRun)
40+
sess, _ := session.New(testInstance, session.LongRun, "")
4141
cookie, _ := sess.ToCookie()
4242
t.Logf("cookie: %q", cookie)
4343
c.SetCookie(cookie)

web/apps/apps.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -797,7 +797,7 @@ func openWebapp(c echo.Context) error {
797797
cookie.HttpOnly = true
798798
cookie.SameSite = http.SameSiteLaxMode
799799
} else {
800-
sess, err = session.New(inst, session.NormalRun)
800+
sess, err = session.New(inst, session.NormalRun, "")
801801
if err != nil {
802802
return wrapAppsError(err)
803803
}

web/apps/apps_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ func TestApps(t *testing.T) {
9292

9393
ts := setup.GetTestServer("/apps", webApps.WebappsRoutes, func(r *echo.Echo) *echo.Echo {
9494
r.POST("/login", func(c echo.Context) error {
95-
sess, _ := session.New(testInstance, session.LongRun)
95+
sess, _ := session.New(testInstance, session.LongRun, "")
9696
cookie, _ := sess.ToCookie()
9797
c.SetCookie(cookie)
9898
return c.HTML(http.StatusOK, "OK")

web/apps/serve.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ func ServeAppFile(c echo.Context, i *instance.Instance, fs appfs.FileServer, web
162162
// reused, even if the user is already logged in and we don't want to
163163
// create a new session
164164
if checked := i.CheckAndClearSessionCode(code); checked && !isLoggedIn {
165-
sessionID, err := auth.SetCookieForNewSession(c, session.NormalRun)
165+
sessionID, err := auth.SetCookieForNewSession(c, session.NormalRun, "")
166166
req := c.Request()
167167
if err == nil {
168168
if err = session.StoreNewLoginEntry(i, sessionID, "", req, "session_code", false); err != nil {

web/auth/auth.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -100,9 +100,9 @@ func Home(c echo.Context) error {
100100
}
101101

102102
// SetCookieForNewSession creates a new session and sets the cookie on echo context
103-
func SetCookieForNewSession(c echo.Context, duration session.Duration) (string, error) {
103+
func SetCookieForNewSession(c echo.Context, duration session.Duration, sid string) (string, error) {
104104
instance := middlewares.GetInstance(c)
105-
session, err := session.New(instance, duration)
105+
session, err := session.New(instance, duration, sid)
106106
if err != nil {
107107
return "", err
108108
}
@@ -255,7 +255,7 @@ func loginForm(c echo.Context) error {
255255
if err != nil {
256256
instance.Logger().Warnf("Delegated token check failed: %s", err)
257257
} else {
258-
sessionID, err := SetCookieForNewSession(c, session.NormalRun)
258+
sessionID, err := SetCookieForNewSession(c, session.NormalRun, "")
259259
if err != nil {
260260
return err
261261
}
@@ -284,7 +284,7 @@ func newSession(c echo.Context, inst *instance.Instance, redirect *url.URL, dura
284284
duration = session.ShortRun
285285
}
286286

287-
sessionID, err := SetCookieForNewSession(c, duration)
287+
sessionID, err := SetCookieForNewSession(c, duration, "")
288288
if err != nil {
289289
return err
290290
}

web/auth/oauth.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ func (a *AuthorizeHTTPHandler) authorizeForm(c echo.Context) error {
182182
// reused, even if the user is already logged in and we don't want to
183183
// create a new session
184184
if checked := inst.CheckAndClearSessionCode(code); checked && !isLoggedIn {
185-
sessionID, err := SetCookieForNewSession(c, session.ShortRun)
185+
sessionID, err := SetCookieForNewSession(c, session.ShortRun, "")
186186
req := c.Request()
187187
if err == nil {
188188
if err = session.StoreNewLoginEntry(inst, sessionID, "", req, "session_code", false); err != nil {

0 commit comments

Comments
 (0)