main.go 3.6 KB
Newer Older
Sietse Ringers's avatar
Sietse Ringers committed
1
2
3
4
// Package irmaserver is a library that allows IRMA verifiers, issuers or attribute-based signature
// applications to perform IRMA sessions with irmaclient instances (i.e. the IRMA app). It exposes
// functions for handling IRMA sessions and a HTTP handler that handles the sessions with the
// irmaclient.
5
package irmaserver
6
7
8
9
10

import (
	"io/ioutil"
	"net/http"

11
	"github.com/go-errors/errors"
12
	"github.com/privacybydesign/irmago"
13
	"github.com/privacybydesign/irmago/internal/servercore"
Sietse Ringers's avatar
Sietse Ringers committed
14
	"github.com/privacybydesign/irmago/server"
15
16
)

Sietse Ringers's avatar
Sietse Ringers committed
17
// Server is an irmaserver instance.
18
type Server struct {
19
	*servercore.Server
20
21
22
	handlers map[string]SessionHandler
}

23
24
// SessionHandler is a function that can handle a session result
// once an IRMA session has completed.
Sietse Ringers's avatar
Sietse Ringers committed
25
type SessionHandler func(*server.SessionResult)
26

Sietse Ringers's avatar
Sietse Ringers committed
27
// New creates a new Server instance with the specified configuration.
28
func New(conf *server.Configuration) (*Server, error) {
29
	s, err := servercore.New(conf)
30
31
32
33
34
35
36
	if err != nil {
		return nil, err
	}
	return &Server{
		Server:   s,
		handlers: make(map[string]SessionHandler),
	}, nil
37
38
}

39
40
41
// StartSession starts an IRMA session, running the handler on completion, if specified.
// The session token (the second return parameter) can be used in GetSessionResult()
// and CancelSession().
42
43
func (s *Server) StartSession(request interface{}, handler SessionHandler) (*irma.Qr, string, error) {
	qr, token, err := s.Server.StartSession(request)
44
45
46
47
	if err != nil {
		return nil, "", err
	}
	if handler != nil {
48
		s.handlers[token] = handler
49
50
51
52
	}
	return qr, token, nil
}

Sietse Ringers's avatar
Sietse Ringers committed
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
// GetSessionResult retrieves the result of the specified IRMA session.
func (s *Server) GetSessionResult(token string) *server.SessionResult {
	return s.Server.GetSessionResult(token)
}

// GetRequest retrieves the request submitted by the requestor that started the specified IRMA session.
func (s *Server) GetRequest(token string) irma.RequestorRequest {
	return s.Server.GetRequest(token)
}

// CancelSession cancels the specified IRMA session.
func (s *Server) CancelSession(token string) error {
	return s.Server.CancelSession(token)
}

// SubscribeServerSentEvents subscribes the HTTP client to server sent events on status updates
// of the specified IRMA session.
func (s *Server) SubscribeServerSentEvents(w http.ResponseWriter, r *http.Request, token string) error {
	return s.Server.SubscribeServerSentEvents(w, r, token)
}

Sietse Ringers's avatar
Sietse Ringers committed
74
// HandlerFunc returns a http.HandlerFunc that handles the IRMA protocol
75
// with IRMA apps.
76
77
//
// Example usage:
Sietse Ringers's avatar
Sietse Ringers committed
78
//   http.HandleFunc("/irma/", irmaserver.HandlerFunc())
79
80
//
// The IRMA app can then perform IRMA sessions at https://example.com/irma.
Sietse Ringers's avatar
Sietse Ringers committed
81
func (s *Server) HandlerFunc() http.HandlerFunc {
82
83
	return func(w http.ResponseWriter, r *http.Request) {
		var message []byte
84
85
86
87
88
89
90
91
		var err error
		if r.Method == http.MethodPost {
			if message, err = ioutil.ReadAll(r.Body); err != nil {
				w.WriteHeader(http.StatusInternalServerError)
				return
			}
		}

92
		token, noun, err := servercore.ParsePath(r.URL.Path)
93
		if err == nil && noun == "statusevents" { // if err != nil we let it be handled by HandleProtocolMessage below
94
			if err = s.SubscribeServerSentEvents(w, r, token); err != nil {
95
96
				server.WriteError(w, server.ErrorUnexpectedRequest, err.Error())
			}
97
98
			return
		}
99

100
		status, response, result := s.HandleProtocolMessage(r.URL.Path, r.Method, r.Header, message)
101
		w.WriteHeader(status)
102
103
104
105
		_, err = w.Write(response)
		if err != nil {
			_ = server.LogError(errors.WrapPrefix(err, "http.ResponseWriter.Write() returned error", 0))
		}
106
		if result != nil && result.Status.Finished() {
107
			if handler := s.handlers[result.Token]; handler != nil {
108
109
110
111
112
				go handler(result)
			}
		}
	}
}