1// Copyright 2020 The Gitea Authors. All rights reserved.
2// Use of this source code is governed by a MIT-style
3// license that can be found in the LICENSE file.
4
5package auth
6
7import (
8	"fmt"
9
10	"code.gitea.io/gitea/models/db"
11	"code.gitea.io/gitea/modules/timeutil"
12)
13
14// Session represents a session compatible for go-chi session
15type Session struct {
16	Key    string             `xorm:"pk CHAR(16)"` // has to be Key to match with go-chi/session
17	Data   []byte             `xorm:"BLOB"`        // on MySQL this has a maximum size of 64Kb - this may need to be increased
18	Expiry timeutil.TimeStamp // has to be Expiry to match with go-chi/session
19}
20
21func init() {
22	db.RegisterModel(new(Session))
23}
24
25// UpdateSession updates the session with provided id
26func UpdateSession(key string, data []byte) error {
27	_, err := db.GetEngine(db.DefaultContext).ID(key).Update(&Session{
28		Data:   data,
29		Expiry: timeutil.TimeStampNow(),
30	})
31	return err
32}
33
34// ReadSession reads the data for the provided session
35func ReadSession(key string) (*Session, error) {
36	session := Session{
37		Key: key,
38	}
39
40	ctx, committer, err := db.TxContext()
41	if err != nil {
42		return nil, err
43	}
44	defer committer.Close()
45
46	if has, err := db.GetByBean(ctx, &session); err != nil {
47		return nil, err
48	} else if !has {
49		session.Expiry = timeutil.TimeStampNow()
50		if err := db.Insert(ctx, &session); err != nil {
51			return nil, err
52		}
53	}
54
55	return &session, committer.Commit()
56}
57
58// ExistSession checks if a session exists
59func ExistSession(key string) (bool, error) {
60	session := Session{
61		Key: key,
62	}
63	return db.GetEngine(db.DefaultContext).Get(&session)
64}
65
66// DestroySession destroys a session
67func DestroySession(key string) error {
68	_, err := db.GetEngine(db.DefaultContext).Delete(&Session{
69		Key: key,
70	})
71	return err
72}
73
74// RegenerateSession regenerates a session from the old id
75func RegenerateSession(oldKey, newKey string) (*Session, error) {
76	ctx, committer, err := db.TxContext()
77	if err != nil {
78		return nil, err
79	}
80	defer committer.Close()
81
82	if has, err := db.GetByBean(ctx, &Session{
83		Key: newKey,
84	}); err != nil {
85		return nil, err
86	} else if has {
87		return nil, fmt.Errorf("session Key: %s already exists", newKey)
88	}
89
90	if has, err := db.GetByBean(ctx, &Session{
91		Key: oldKey,
92	}); err != nil {
93		return nil, err
94	} else if !has {
95		if err := db.Insert(ctx, &Session{
96			Key:    oldKey,
97			Expiry: timeutil.TimeStampNow(),
98		}); err != nil {
99			return nil, err
100		}
101	}
102
103	if _, err := db.Exec(ctx, "UPDATE "+db.TableName(&Session{})+" SET `key` = ? WHERE `key`=?", newKey, oldKey); err != nil {
104		return nil, err
105	}
106
107	s := Session{
108		Key: newKey,
109	}
110	if _, err := db.GetByBean(ctx, &s); err != nil {
111		return nil, err
112	}
113
114	return &s, committer.Commit()
115}
116
117// CountSessions returns the number of sessions
118func CountSessions() (int64, error) {
119	return db.GetEngine(db.DefaultContext).Count(&Session{})
120}
121
122// CleanupSessions cleans up expired sessions
123func CleanupSessions(maxLifetime int64) error {
124	_, err := db.GetEngine(db.DefaultContext).Where("expiry <= ?", timeutil.TimeStampNow().Add(-maxLifetime)).Delete(&Session{})
125	return err
126}
127