Skip to content
GitLab
Menu
Projects
Groups
Snippets
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
IRMA
Github mirrors
irmago
Commits
95d5da10
Commit
95d5da10
authored
Mar 06, 2020
by
David Venhoek
Committed by
Sietse Ringers
Dec 11, 2020
Browse files
Completed email login/logout flow.
parent
04bfcb72
Changes
4
Hide whitespace changes
Inline
Side-by-side
server/myirmaserver/conf.go
View file @
95d5da10
package
myirmaserver
import
(
"html/template"
"net/smtp"
"strings"
...
...
@@ -61,10 +62,14 @@ type Configuration struct {
KeyshareAttributes
[]
irma
.
AttributeTypeIdentifier
// Configuration for email sending during login (email address use will be disabled if not present)
EmailServer
string
EmailAuth
smtp
.
Auth
EmailFrom
string
DefaultLanguage
string
EmailServer
string
EmailAuth
smtp
.
Auth
EmailFrom
string
DefaultLanguage
string
LoginEmailFiles
map
[
string
]
string
LoginEmailTemplates
map
[
string
]
*
template
.
Template
LoginEmailSubject
map
[
string
]
string
LoginEmailBaseURL
map
[
string
]
string
// Logging verbosity level: 0 is normal, 1 includes DEBUG level, 2 includes TRACE level
Verbose
int
`json:"verbose" mapstructure:"verbose"`
...
...
@@ -117,11 +122,29 @@ func processConfiguration(conf *Configuration) error {
}
}
// TODO: Setup email templates
// Setup email templates
if
conf
.
EmailServer
!=
""
&&
conf
.
LoginEmailTemplates
==
nil
{
conf
.
LoginEmailTemplates
=
map
[
string
]
*
template
.
Template
{}
for
lang
,
templateFile
:=
range
conf
.
LoginEmailFiles
{
var
err
error
conf
.
LoginEmailTemplates
[
lang
],
err
=
template
.
ParseFiles
(
templateFile
)
if
err
!=
nil
{
return
server
.
LogError
(
err
)
}
}
}
// Verify email configuration
if
conf
.
EmailServer
!=
""
{
// TODO
if
_
,
ok
:=
conf
.
LoginEmailTemplates
[
conf
.
DefaultLanguage
];
!
ok
{
return
server
.
LogError
(
errors
.
Errorf
(
"Missing login email template for default language"
))
}
if
_
,
ok
:=
conf
.
LoginEmailSubject
[
conf
.
DefaultLanguage
];
!
ok
{
return
server
.
LogError
(
errors
.
Errorf
(
"Missing login email subject for default language"
))
}
if
_
,
ok
:=
conf
.
LoginEmailBaseURL
[
conf
.
DefaultLanguage
];
!
ok
{
return
server
.
LogError
(
errors
.
Errorf
(
"Missing login email base url for default language"
))
}
}
// Setup database
...
...
server/myirmaserver/db.go
View file @
95d5da10
...
...
@@ -3,30 +3,44 @@ package myirmaserver
import
(
"errors"
"sync"
"time"
)
var
(
ErrUserAlreadyExists
=
errors
.
New
(
"Cannot create user, username already taken"
)
ErrUserNotFound
=
errors
.
New
(
"Could not find specified user"
)
ErrInvalidRecord
=
errors
.
New
(
"Invalid record in database"
)
ErrUserNotFound
=
errors
.
New
(
"Could not find specified user"
)
)
type
MyirmaDB
interface
{
GetUserID
(
username
string
)
(
int64
,
error
)
AddEmailLoginToken
(
email
,
token
string
)
error
LoginTokenGetCandidates
(
token
string
)
([]
LoginCandidate
,
error
)
LoginTokenGetEmail
(
token
string
)
(
string
,
error
)
TryUserLoginToken
(
token
,
username
string
)
(
bool
,
error
)
}
type
LoginCandidate
struct
{
Username
string
`json:"username"`
LastActive
int64
`json:"last_active"`
}
type
MemoryUserData
struct
{
ID
int64
ID
int64
Email
string
LastActive
time
.
Time
}
type
MyirmaMemoryDB
struct
{
lock
sync
.
Mutex
UserData
map
[
string
]
MemoryUserData
LoginEmailTokens
map
[
string
]
string
}
func
NewMyirmaMemoryDB
()
MyirmaDB
{
return
&
MyirmaMemoryDB
{
UserData
:
map
[
string
]
MemoryUserData
{},
UserData
:
map
[
string
]
MemoryUserData
{},
LoginEmailTokens
:
map
[
string
]
string
{},
}
}
...
...
@@ -39,3 +53,73 @@ func (db *MyirmaMemoryDB) GetUserID(username string) (int64, error) {
}
return
data
.
ID
,
nil
}
func
(
db
*
MyirmaMemoryDB
)
AddEmailLoginToken
(
email
,
token
string
)
error
{
db
.
lock
.
Lock
()
defer
db
.
lock
.
Unlock
()
found
:=
false
for
_
,
v
:=
range
db
.
UserData
{
if
v
.
Email
==
email
{
found
=
true
break
}
}
if
!
found
{
return
ErrUserNotFound
}
db
.
LoginEmailTokens
[
token
]
=
email
return
nil
}
func
(
db
*
MyirmaMemoryDB
)
LoginTokenGetCandidates
(
token
string
)
([]
LoginCandidate
,
error
)
{
db
.
lock
.
Lock
()
defer
db
.
lock
.
Unlock
()
email
,
ok
:=
db
.
LoginEmailTokens
[
token
]
if
!
ok
{
return
nil
,
ErrUserNotFound
}
result
:=
[]
LoginCandidate
{}
for
k
,
v
:=
range
db
.
UserData
{
if
v
.
Email
==
email
{
result
=
append
(
result
,
LoginCandidate
{
Username
:
k
,
LastActive
:
v
.
LastActive
.
Unix
()})
}
}
return
result
,
nil
}
func
(
db
*
MyirmaMemoryDB
)
LoginTokenGetEmail
(
token
string
)
(
string
,
error
)
{
db
.
lock
.
Lock
()
defer
db
.
lock
.
Unlock
()
v
,
ok
:=
db
.
LoginEmailTokens
[
token
]
if
!
ok
{
return
""
,
ErrUserNotFound
}
return
v
,
nil
}
func
(
db
*
MyirmaMemoryDB
)
TryUserLoginToken
(
token
,
username
string
)
(
bool
,
error
)
{
db
.
lock
.
Lock
()
defer
db
.
lock
.
Unlock
()
email
,
ok
:=
db
.
LoginEmailTokens
[
token
]
if
!
ok
{
return
false
,
nil
}
user
,
ok
:=
db
.
UserData
[
username
]
if
!
ok
{
return
false
,
ErrUserNotFound
}
if
user
.
Email
==
email
{
delete
(
db
.
LoginEmailTokens
,
token
)
return
true
,
nil
}
else
{
return
false
,
nil
}
}
server/myirmaserver/server.go
View file @
95d5da10
package
myirmaserver
import
(
"bytes"
"encoding/json"
"io/ioutil"
"net/http"
"time"
...
...
@@ -56,6 +59,10 @@ func (s *Server) Handler() http.Handler {
router
:=
chi
.
NewRouter
()
router
.
Post
(
"/checksession"
,
s
.
handleCheckSession
)
router
.
Post
(
"/login/irma"
,
s
.
handleIrmaLogin
)
router
.
Post
(
"/login/email"
,
s
.
handleEmailLogin
)
router
.
Post
(
"/login/token/candidates"
,
s
.
handleGetCandidates
)
router
.
Post
(
"/login/token"
,
s
.
handleTokenLogin
)
router
.
Post
(
"/logout"
,
s
.
handleLogout
)
router
.
Mount
(
"/irma/"
,
s
.
sessionserver
.
HandlerFunc
())
if
s
.
conf
.
StaticPath
!=
""
{
...
...
@@ -90,6 +97,161 @@ func (s *Server) handleCheckSession(w http.ResponseWriter, r *http.Request) {
}
}
type
EmailLoginRequest
struct
{
Email
string
`json:"email"`
Language
string
`json:"language"`
}
func
(
s
*
Server
)
handleEmailLogin
(
w
http
.
ResponseWriter
,
r
*
http
.
Request
)
{
if
s
.
conf
.
EmailServer
==
""
{
server
.
WriteError
(
w
,
server
.
ErrorInternal
,
"not enabled in configuration"
)
return
}
requestData
,
err
:=
ioutil
.
ReadAll
(
r
.
Body
)
if
err
!=
nil
{
s
.
conf
.
Logger
.
WithField
(
"error"
,
err
)
.
Info
(
"Malformed request: could not read request body"
)
server
.
WriteError
(
w
,
server
.
ErrorInvalidRequest
,
err
.
Error
())
return
}
var
request
EmailLoginRequest
err
=
json
.
Unmarshal
(
requestData
,
&
request
)
if
err
!=
nil
{
s
.
conf
.
Logger
.
WithField
(
"error"
,
err
)
.
Info
(
"Malformed request: could not parse request body"
)
server
.
WriteError
(
w
,
server
.
ErrorInvalidRequest
,
err
.
Error
())
return
}
token
:=
server
.
NewSessionToken
()
err
=
s
.
db
.
AddEmailLoginToken
(
request
.
Email
,
token
)
if
err
==
ErrUserNotFound
{
server
.
WriteError
(
w
,
server
.
ErrorUserNotRegistered
,
""
)
return
}
else
if
err
!=
nil
{
s
.
conf
.
Logger
.
WithField
(
"error"
,
err
)
.
Error
(
"Error adding login token to database"
)
server
.
WriteError
(
w
,
server
.
ErrorInternal
,
err
.
Error
())
return
}
template
,
ok
:=
s
.
conf
.
LoginEmailTemplates
[
request
.
Language
]
if
!
ok
{
template
=
s
.
conf
.
LoginEmailTemplates
[
s
.
conf
.
DefaultLanguage
]
}
subject
,
ok
:=
s
.
conf
.
LoginEmailSubject
[
request
.
Language
]
if
!
ok
{
subject
=
s
.
conf
.
LoginEmailSubject
[
s
.
conf
.
DefaultLanguage
]
}
baseURL
,
ok
:=
s
.
conf
.
LoginEmailBaseURL
[
request
.
Language
]
if
!
ok
{
baseURL
=
s
.
conf
.
LoginEmailBaseURL
[
s
.
conf
.
DefaultLanguage
]
}
var
emsg
bytes
.
Buffer
err
=
template
.
Execute
(
&
emsg
,
map
[
string
]
string
{
"TokenURL"
:
baseURL
+
token
})
if
err
!=
nil
{
s
.
conf
.
Logger
.
WithField
(
"error"
,
err
)
.
Error
(
"Could not generate login mail from template"
)
server
.
WriteError
(
w
,
server
.
ErrorInternal
,
err
.
Error
())
return
}
err
=
server
.
SendHTMLMail
(
s
.
conf
.
EmailServer
,
s
.
conf
.
EmailAuth
,
s
.
conf
.
EmailFrom
,
request
.
Email
,
subject
,
emsg
.
Bytes
())
if
err
!=
nil
{
s
.
conf
.
Logger
.
WithField
(
"error"
,
err
)
.
Error
(
"Could not send login mail"
)
server
.
WriteError
(
w
,
server
.
ErrorInternal
,
err
.
Error
())
return
}
w
.
WriteHeader
(
http
.
StatusNoContent
)
// No need for content.
}
func
(
s
*
Server
)
handleGetCandidates
(
w
http
.
ResponseWriter
,
r
*
http
.
Request
)
{
requestData
,
err
:=
ioutil
.
ReadAll
(
r
.
Body
)
if
err
!=
nil
{
s
.
conf
.
Logger
.
WithField
(
"error"
,
err
)
.
Info
(
"Malformed request: could not read body"
)
server
.
WriteError
(
w
,
server
.
ErrorInvalidRequest
,
"could not read request body"
)
return
}
token
:=
string
(
requestData
)
candidates
,
err
:=
s
.
db
.
LoginTokenGetCandidates
(
token
)
if
err
==
ErrUserNotFound
{
server
.
WriteError
(
w
,
server
.
ErrorInvalidRequest
,
"token invalid"
)
return
}
else
if
err
!=
nil
{
s
.
conf
.
Logger
.
WithField
(
"error"
,
err
)
.
Error
(
"Could not retrieve candidates for token"
)
server
.
WriteError
(
w
,
server
.
ErrorInternal
,
err
.
Error
())
return
}
server
.
WriteJson
(
w
,
candidates
)
}
type
TokenLoginRequest
struct
{
Token
string
`json:"token"`
Username
string
`json:"username"`
}
func
(
s
*
Server
)
handleTokenLogin
(
w
http
.
ResponseWriter
,
r
*
http
.
Request
)
{
requestData
,
err
:=
ioutil
.
ReadAll
(
r
.
Body
)
if
err
!=
nil
{
s
.
conf
.
Logger
.
WithField
(
"error"
,
err
)
.
Info
(
"Malformed request: could not read body"
)
server
.
WriteError
(
w
,
server
.
ErrorInvalidRequest
,
"could not read request body"
)
return
}
var
request
TokenLoginRequest
err
=
json
.
Unmarshal
(
requestData
,
&
request
)
if
err
!=
nil
{
s
.
conf
.
Logger
.
WithField
(
"error"
,
err
)
.
Info
(
"Malformed request: could not parse request body"
)
server
.
WriteError
(
w
,
server
.
ErrorInvalidRequest
,
err
.
Error
())
return
}
ok
,
err
:=
s
.
db
.
TryUserLoginToken
(
request
.
Token
,
request
.
Username
)
if
err
==
ErrUserNotFound
{
server
.
WriteError
(
w
,
server
.
ErrorInvalidRequest
,
"Invalid login request"
)
return
}
if
err
!=
nil
{
s
.
conf
.
Logger
.
WithField
(
"error"
,
err
)
.
Error
(
"Could not login user using token"
)
server
.
WriteError
(
w
,
server
.
ErrorInvalidRequest
,
err
.
Error
())
return
}
if
!
ok
{
server
.
WriteError
(
w
,
server
.
ErrorInvalidRequest
,
"Invalid login request"
)
return
}
session
:=
s
.
store
.
create
()
session
.
userID
=
new
(
int64
)
*
session
.
userID
,
err
=
s
.
db
.
GetUserID
(
request
.
Username
)
// username is trusted, since it was validated by s.db.TryUserLoginToken
if
err
!=
nil
{
s
.
conf
.
Logger
.
WithField
(
"error"
,
err
)
.
Error
(
"Could not fetch userid for username validated in earlier step"
)
server
.
WriteError
(
w
,
server
.
ErrorInternal
,
err
.
Error
())
return
}
token
:=
session
.
token
http
.
SetCookie
(
w
,
&
http
.
Cookie
{
Name
:
"session"
,
Value
:
token
,
MaxAge
:
s
.
conf
.
SessionLifetime
,
Secure
:
s
.
conf
.
Production
,
Path
:
"/"
,
HttpOnly
:
true
,
})
w
.
WriteHeader
(
http
.
StatusNoContent
)
}
func
(
s
*
Server
)
handleIrmaLogin
(
w
http
.
ResponseWriter
,
r
*
http
.
Request
)
{
session
:=
s
.
store
.
create
()
sessiontoken
:=
session
.
token
...
...
@@ -112,6 +274,7 @@ func (s *Server) handleIrmaLogin(w http.ResponseWriter, r *http.Request) {
session
.
pendingErrorMessage
=
""
return
}
else
if
err
!=
nil
{
s
.
conf
.
Logger
.
WithField
(
"error"
,
err
)
.
Error
(
"Error during processing of login irma session result"
)
session
.
pendingError
=
&
server
.
ErrorInternal
session
.
pendingErrorMessage
=
err
.
Error
()
return
...
...
@@ -122,6 +285,7 @@ func (s *Server) handleIrmaLogin(w http.ResponseWriter, r *http.Request) {
})
if
err
!=
nil
{
s
.
conf
.
Logger
.
WithField
(
"error"
,
err
)
.
Error
(
"Error during startup of irma session for login"
)
server
.
WriteError
(
w
,
server
.
ErrorInternal
,
err
.
Error
())
return
}
...
...
@@ -137,6 +301,17 @@ func (s *Server) handleIrmaLogin(w http.ResponseWriter, r *http.Request) {
server
.
WriteJson
(
w
,
qr
)
}
func
(
s
*
Server
)
handleLogout
(
w
http
.
ResponseWriter
,
r
*
http
.
Request
)
{
http
.
SetCookie
(
w
,
&
http
.
Cookie
{
Name
:
"session"
,
Value
:
""
,
Secure
:
s
.
conf
.
Production
,
Path
:
"/"
,
HttpOnly
:
true
,
})
w
.
WriteHeader
(
http
.
StatusNoContent
)
}
func
(
s
*
Server
)
StaticFilesHandler
()
http
.
Handler
{
return
http
.
StripPrefix
(
s
.
conf
.
StaticPrefix
,
http
.
FileServer
(
http
.
Dir
(
s
.
conf
.
StaticPath
)))
}
server/myirmaserver/tmpmain/main.go
View file @
95d5da10
...
...
@@ -10,9 +10,11 @@ func main() {
db
:=
&
myirmaserver
.
MyirmaMemoryDB
{
UserData
:
map
[
string
]
myirmaserver
.
MemoryUserData
{
"rgBpfxdwfE"
:
myirmaserver
.
MemoryUserData
{
ID
:
1
,
ID
:
1
,
Email
:
"test@test.com"
,
},
},
LoginEmailTokens
:
map
[
string
]
string
{},
}
s
,
err
:=
myirmaserver
.
New
(
&
myirmaserver
.
Configuration
{
URL
:
"http://127.0.0.1:8080"
,
...
...
@@ -20,6 +22,12 @@ func main() {
StaticPrefix
:
"/test/"
,
DB
:
db
,
KeyshareAttributeNames
:
[]
string
{
"pbdf.sidn-pbdf.irma.pseudonym"
},
EmailServer
:
"localhost:1025"
,
EmailFrom
:
"test@example.com"
,
DefaultLanguage
:
"en"
,
LoginEmailFiles
:
map
[
string
]
string
{
"en"
:
"testtemplate.html"
},
LoginEmailSubject
:
map
[
string
]
string
{
"en"
:
"Login MyIRMA"
},
LoginEmailBaseURL
:
map
[
string
]
string
{
"en"
:
"http://127.0.0.1:8080/test/#token="
},
})
if
err
!=
nil
{
panic
(
err
)
...
...
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment