/
main.go
192 lines (170 loc) · 5.77 KB
/
main.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
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
package main
import (
"encoding/base64"
"encoding/json"
"log"
"net"
"net/http"
"strings"
"github.com/hossner/bankid"
"github.com/rs/xid"
"github.com/gorilla/websocket"
)
// bidConn holds the connection with the server
var bidConn *bankid.Connection
// queueToClient is used to transfer messages from server (through call back function) to web client
var queueToClient = make(chan *wsMsg)
// queueFromClient is used to transfer messages from web client to server
var queueFromClient = make(chan *wsMsg)
// upgrader upgrades the HTTP connection to a websocket connection
var upgrader = websocket.Upgrader{}
// sessMap maps the client session IDs with the channels to correct go routine (that handles the web socket)
var sessMap = make(map[string]chan *wsMsg)
type wsMsg struct {
Action string `json:"action"`
Value string `json:"value"`
SessID string `json:"id"`
IPAddr string
}
func main() {
// Set up simple web server for www directory
fs := http.FileServer(http.Dir("www"))
http.Handle("/", http.StripPrefix("/", fs))
// Set up handler for the websocket request from client
http.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) {
// Accept upgrade from any origin (don't do this in a production environment!)
upgrader.CheckOrigin = func(r *http.Request) bool { return true }
// Upgrade the http request to a websocket
var conn, err = upgrader.Upgrade(w, r, nil)
if err != nil {
log.Fatalf("could not upgrade request to websocket: %v", err)
}
// Create a session ID for the queues to and from the client
qid := xid.New().String()
// Create a channel to write server responses to, to the right session
sessMap[qid] = make(chan *wsMsg)
// Start a go routine used to send requests to the web client
go socketWriter(conn, qid)
// Start a go routine to listen to incomming requests from the web client
go socketReader(conn, qid, getRealAddr(r))
})
// The config file name defaults by the library to 'config.json' in the application working directory
cfgFileName := ""
// Create a new connection to the BankID server
bidConn, err := bankid.New(cfgFileName, callBack)
if err != nil {
log.Fatalf("failed to create a connection to the BankID service: %v", err)
}
defer bidConn.Close()
// Start a go routine to handle requests from clients
go handleClients(bidConn)
// Start web server, listening to port 8080
log.Println("Listening to port 8080...")
http.ListenAndServe(":8080", nil)
}
// Incomming messages from the BankID connection are put on the queue to the client
func callBack(reqID, msg, detail string) {
/*
Possible values for 'msg':
Non error messages:
'sent': autoStartToken returned as detail
'cancelled': Caller cancelled the transaction
'outstandingTransaction': Waiting for user to start BankID client
'noClient': Client has not yet received the transaction
'started': Client started, user is handling the transaction
'userSign':
Error messages:
'error': Internal error from the library, e.g. malformed arguments etc.
'alreadyInProgress':
'invalidParameters':
'unauthorized':
'notFound':
'requestTimeout':
'unsupportedMediaType':
'internalError':
'maintenance':
// If 'failed':
'expiredTransaction':
'certificateErr':
'userCancel': User aborted/cancelled the transaction
'cancelled': New transaction for the same individual started
'startFailed':
*/
// Create a new instance of wsMsg to hold the values from the server
newMsg := wsMsg{Action: msg, Value: detail, SessID: reqID}
// In this example we just push the message to the client through the socketWriter
sessMap[reqID] <- &newMsg
// queueToClient <- &newMsg
}
// Poll the queueToClient and send incomming messages to the client
func socketWriter(wsConn *websocket.Conn, id string) {
more := true
for more {
ms, more := <-sessMap[id]
if more {
wsConn.WriteJSON(ms)
}
}
}
// Listen to requests from the client and put them on the queue from the client
func socketReader(wsConn *websocket.Conn, id, ip string) {
for {
_, msg, err := wsConn.ReadMessage()
if err != nil {
// This occurs when the client has sent a 'close' message for the websocket
wsConn.Close()
close(sessMap[id])
sessMap[id] = nil
delete(sessMap, id)
}
var newMsg wsMsg
err = json.Unmarshal(msg, &newMsg)
if err != nil {
log.Printf("could not unmarshal request from web client: %v:\n", err)
return
}
newMsg.SessID = id
newMsg.IPAddr = ip
queueFromClient <- &newMsg
}
}
func onQrGen(qrBytes []byte, reqID string) {
newMsg := wsMsg{Action: "qrcode", Value: base64.StdEncoding.EncodeToString(qrBytes), SessID: reqID}
sessMap[reqID] <- &newMsg
}
// Poll the queueFromClient and send incomming messages to the server
func handleClients(bConn *bankid.Connection) {
for {
msg := <-queueFromClient
switch msg.Action {
case "pnrAuth":
// The web client sent a pnr and requests an authentication
reqs := bankid.Requirements{PersonalNumber: msg.Value}
bConn.SendRequest(msg.IPAddr, msg.SessID, "", &reqs, nil)
case "qrCode":
// reqs := bankid.Requirements{TokenStartRequired: true}
// bConn.SendRequest(msg.IPAddr, msg.SessID, "", &reqs, onQrGen)
bConn.SendRequest(msg.IPAddr, msg.SessID, "", &bankid.Requirements{TokenStartRequired: true}, onQrGen)
default:
log.Println("Unknown command:", "\""+msg.Action+"\"")
}
}
}
func getRealAddr(r *http.Request) string {
if xff := strings.Trim(r.Header.Get("X-Forwarded-For"), ","); len(xff) > 0 {
addrs := strings.Split(xff, ",")
lastFwd := addrs[len(addrs)-1]
if ip := net.ParseIP(lastFwd); ip != nil {
return ip.String()
}
}
if xri := r.Header.Get("X-Real-Ip"); len(xri) > 0 {
if ip := net.ParseIP(xri); ip != nil {
return ip.String()
}
}
if parts := strings.Split(r.RemoteAddr, ":"); len(parts) == 2 {
return parts[0]
}
return ""
}