/
session_create.go
133 lines (110 loc) Β· 4.08 KB
/
session_create.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
package api
import (
"errors"
"net/http"
"github.com/gin-gonic/gin"
"github.com/photoprism/photoprism/internal/entity"
"github.com/photoprism/photoprism/internal/event"
"github.com/photoprism/photoprism/internal/form"
"github.com/photoprism/photoprism/internal/get"
"github.com/photoprism/photoprism/internal/server/limiter"
"github.com/photoprism/photoprism/pkg/authn"
"github.com/photoprism/photoprism/pkg/header"
"github.com/photoprism/photoprism/pkg/i18n"
)
// CreateSession creates a new client session and returns it as JSON if authentication was successful.
//
// POST /api/v1/session
// POST /api/v1/sessions
func CreateSession(router *gin.RouterGroup) {
createSessionHandler := func(c *gin.Context) {
// Prevent CDNs from caching this endpoint.
if header.IsCdn(c.Request) {
AbortNotFound(c)
return
}
var f form.Login
clientIp := ClientIP(c)
// Assign and validate request form values.
if err := c.BindJSON(&f); err != nil {
event.AuditWarn([]string{clientIp, "create session", "invalid request", "%s"}, err)
AbortBadRequest(c)
return
}
// Disable caching of responses.
c.Header(header.CacheControl, header.CacheControlNoStore)
conf := get.Config()
// Skip authentication if app is running in public mode.
if conf.Public() {
sess := get.Session().Public()
// Response includes admin account data, session data, and client config values.
response := CreateSessionResponse(sess.AuthToken(), sess, conf.ClientPublic())
// Return JSON response.
c.JSON(http.StatusOK, response)
return
}
// Check request rate limit.
var r *limiter.Request
if f.HasPasscode() {
r = limiter.Login.RequestN(clientIp, 3)
} else {
r = limiter.Login.Request(clientIp)
}
// Abort if failure rate limit is exceeded.
if r.Reject() || limiter.Auth.Reject(clientIp) {
limiter.AbortJSON(c)
return
}
var sess *entity.Session
var isNew bool
var err error
// Find existing session, if any.
if s := Session(clientIp, AuthToken(c)); s != nil {
// Update existing session.
sess = s
} else {
// Create new session.
sess = get.Session().New(c)
isNew = true
}
// Check authentication credentials.
if err = sess.LogIn(f, c); err != nil {
if sess.Method().IsNot(authn.Method2FA) {
c.AbortWithStatusJSON(sess.HttpStatus(), gin.H{"error": i18n.Msg(i18n.ErrInvalidCredentials)})
} else if errors.Is(err, authn.ErrPasscodeRequired) {
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": err.Error(), "code": i18n.ErrPasscodeRequired, "message": i18n.Msg(i18n.ErrPasscodeRequired)})
// Return the reserved request rate limit tokens if password is correct, even if the verification code is missing.
r.Success()
} else {
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": err.Error(), "code": i18n.ErrInvalidPasscode, "message": i18n.Msg(i18n.ErrInvalidPasscode)})
}
return
}
// Extend session lifetime if 2-Factor Authentication (2FA) is enabled for the account.
if sess.Is2FA() && !sess.IsClient() {
sess.SetExpiresIn(conf.SessionMaxAge() * 2)
sess.SetTimeout(conf.SessionTimeout() * 2)
}
// Save session after successful authentication.
if sess, err = get.Session().Save(sess); err != nil {
event.AuditErr([]string{clientIp, "%s"}, err)
c.AbortWithStatusJSON(sess.HttpStatus(), gin.H{"error": i18n.Msg(i18n.ErrInvalidCredentials)})
return
} else if sess == nil {
c.AbortWithStatusJSON(sess.HttpStatus(), gin.H{"error": i18n.Msg(i18n.ErrUnexpected)})
return
} else if isNew {
event.AuditInfo([]string{clientIp, "session %s", "created"}, sess.RefID)
} else {
event.AuditInfo([]string{clientIp, "session %s", "updated"}, sess.RefID)
}
// Return the reserved request rate limit tokens after successful authentication.
r.Success()
// Response includes user data, session data, and client config values.
response := CreateSessionResponse(sess.AuthToken(), sess, conf.ClientSession(sess))
// Return JSON response.
c.JSON(sess.HttpStatus(), response)
}
router.POST("/session", createSessionHandler)
router.POST("/sessions", createSessionHandler)
}