token-validator: start project
This commit is contained in:
parent
8268fed28c
commit
0fbe142626
1
token-validator/.gitignore
vendored
Normal file
1
token-validator/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
token-validator
|
109
token-validator/db.go
Normal file
109
token-validator/db.go
Normal file
|
@ -0,0 +1,109 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
_ "github.com/go-sql-driver/mysql"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var db *sql.DB
|
||||||
|
|
||||||
|
func DSNGenerator() string {
|
||||||
|
db_user := "adlin"
|
||||||
|
db_password := "adlin"
|
||||||
|
db_host := ""
|
||||||
|
db_db := "adlin"
|
||||||
|
|
||||||
|
if v, exists := os.LookupEnv("MYSQL_HOST"); exists {
|
||||||
|
db_host = v
|
||||||
|
}
|
||||||
|
if v, exists := os.LookupEnv("MYSQL_PASSWORD"); exists {
|
||||||
|
db_password = v
|
||||||
|
} else if v, exists := os.LookupEnv("MYSQL_ROOT_PASSWORD"); exists {
|
||||||
|
db_user = "root"
|
||||||
|
db_password = v
|
||||||
|
}
|
||||||
|
if v, exists := os.LookupEnv("MYSQL_USER"); exists {
|
||||||
|
db_user = v
|
||||||
|
}
|
||||||
|
if v, exists := os.LookupEnv("MYSQL_DATABASE"); exists {
|
||||||
|
db_db = v
|
||||||
|
}
|
||||||
|
|
||||||
|
return db_user + ":" + db_password + "@" + db_host + "/" + db_db
|
||||||
|
}
|
||||||
|
|
||||||
|
func DBInit(dsn string) (err error) {
|
||||||
|
if db, err = sql.Open("mysql", dsn+"?parseTime=true&foreign_key_checks=1"); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = db.Exec(`SET SESSION sql_mode = 'STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION';`)
|
||||||
|
for i := 0; err != nil && i < 5; i += 1 {
|
||||||
|
if _, err = db.Exec(`SET SESSION sql_mode = 'STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION';`); err != nil && i <= 5 {
|
||||||
|
log.Println("An error occurs when trying to connect to DB, will retry in 2 seconds: ", err)
|
||||||
|
time.Sleep(2 * time.Second)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func DBCreate() (err error) {
|
||||||
|
if _, err = db.Exec(`
|
||||||
|
CREATE TABLE IF NOT EXISTS students(
|
||||||
|
id_student INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT,
|
||||||
|
login VARCHAR(255) NOT NULL UNIQUE,
|
||||||
|
time TIMESTAMP NOT NULL
|
||||||
|
) DEFAULT CHARACTER SET = utf8 COLLATE = utf8_bin;
|
||||||
|
`); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if _, err := db.Exec(`
|
||||||
|
CREATE TABLE IF NOT EXISTS student_login(
|
||||||
|
id_student INTEGER,
|
||||||
|
ip VARCHAR(255) NOT NULL,
|
||||||
|
mac VARCHAR(255) NOT NULL,
|
||||||
|
time TIMESTAMP NOT NULL,
|
||||||
|
FOREIGN KEY(id_student) REFERENCES students(id_student)
|
||||||
|
) DEFAULT CHARACTER SET = utf8 COLLATE = utf8_bin;
|
||||||
|
`); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, err := db.Exec(`
|
||||||
|
CREATE TABLE IF NOT EXISTS student_challenges(
|
||||||
|
id_st INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT,
|
||||||
|
id_student INTEGER NOT NULL,
|
||||||
|
challenge INTEGER NOT NULL,
|
||||||
|
time TIMESTAMP NOT NULL,
|
||||||
|
value VARCHAR(255) NOT NULL,
|
||||||
|
CONSTRAINT token_found UNIQUE (id_student,challenge),
|
||||||
|
FOREIGN KEY(id_student) REFERENCES students(id_student)
|
||||||
|
) DEFAULT CHARACTER SET = utf8 COLLATE = utf8_bin;
|
||||||
|
`); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func DBClose() error {
|
||||||
|
return db.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func DBPrepare(query string) (*sql.Stmt, error) {
|
||||||
|
return db.Prepare(query)
|
||||||
|
}
|
||||||
|
|
||||||
|
func DBQuery(query string, args ...interface{}) (*sql.Rows, error) {
|
||||||
|
return db.Query(query, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func DBExec(query string, args ...interface{}) (sql.Result, error) {
|
||||||
|
return db.Exec(query, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func DBQueryRow(query string, args ...interface{}) *sql.Row {
|
||||||
|
return db.QueryRow(query, args...)
|
||||||
|
}
|
80
token-validator/handler.go
Normal file
80
token-validator/handler.go
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/julienschmidt/httprouter"
|
||||||
|
)
|
||||||
|
|
||||||
|
var router = httprouter.New()
|
||||||
|
|
||||||
|
func Router() *httprouter.Router {
|
||||||
|
return router
|
||||||
|
}
|
||||||
|
|
||||||
|
type DispatchFunction func(httprouter.Params, []byte) (interface{}, error)
|
||||||
|
|
||||||
|
func apiHandler(f DispatchFunction) func(http.ResponseWriter, *http.Request, httprouter.Params) {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
||||||
|
if addr := r.Header.Get("X-Forwarded-For"); addr != "" {
|
||||||
|
r.RemoteAddr = addr
|
||||||
|
}
|
||||||
|
log.Printf("%s \"%s %s\" [%s]\n", r.RemoteAddr, r.Method, r.URL.Path, r.UserAgent())
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
|
||||||
|
var ret interface{}
|
||||||
|
var err error = nil
|
||||||
|
|
||||||
|
// Read the body
|
||||||
|
if r.ContentLength < 0 || r.ContentLength > 6553600 {
|
||||||
|
http.Error(w, fmt.Sprintf("{errmsg:\"Request too large or request size unknown\"}", err), http.StatusRequestEntityTooLarge)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var body []byte
|
||||||
|
if r.ContentLength > 0 {
|
||||||
|
tmp := make([]byte, 1024)
|
||||||
|
for {
|
||||||
|
n, err := r.Body.Read(tmp)
|
||||||
|
for j := 0; j < n; j++ {
|
||||||
|
body = append(body, tmp[j])
|
||||||
|
}
|
||||||
|
if err != nil || n <= 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ret, err = f(ps, body)
|
||||||
|
|
||||||
|
// Format response
|
||||||
|
resStatus := http.StatusOK
|
||||||
|
if err != nil {
|
||||||
|
ret = map[string]string{"errmsg": err.Error()}
|
||||||
|
resStatus = http.StatusBadRequest
|
||||||
|
log.Println(r.RemoteAddr, resStatus, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if ret == nil {
|
||||||
|
ret = map[string]string{"errmsg": "Page not found"}
|
||||||
|
resStatus = http.StatusNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
if str, found := ret.(string); found {
|
||||||
|
w.WriteHeader(resStatus)
|
||||||
|
io.WriteString(w, str)
|
||||||
|
} else if bts, found := ret.([]byte); found {
|
||||||
|
w.WriteHeader(resStatus)
|
||||||
|
w.Write(bts)
|
||||||
|
} else if j, err := json.Marshal(ret); err != nil {
|
||||||
|
http.Error(w, fmt.Sprintf("{\"errmsg\":%q}", err), http.StatusInternalServerError)
|
||||||
|
} else {
|
||||||
|
w.WriteHeader(resStatus)
|
||||||
|
w.Write(j)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
0
token-validator/htdocs/index.html
Normal file
0
token-validator/htdocs/index.html
Normal file
95
token-validator/main.go
Normal file
95
token-validator/main.go
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var sharedSecret string
|
||||||
|
var StaticDir string
|
||||||
|
|
||||||
|
type ResponseWriterPrefix struct {
|
||||||
|
real http.ResponseWriter
|
||||||
|
prefix string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r ResponseWriterPrefix) Header() http.Header {
|
||||||
|
return r.real.Header()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r ResponseWriterPrefix) WriteHeader(s int) {
|
||||||
|
if v, exists := r.real.Header()["Location"]; exists {
|
||||||
|
r.real.Header().Set("Location", r.prefix+v[0])
|
||||||
|
}
|
||||||
|
r.real.WriteHeader(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r ResponseWriterPrefix) Write(z []byte) (int, error) {
|
||||||
|
return r.real.Write(z)
|
||||||
|
}
|
||||||
|
|
||||||
|
func StripPrefix(prefix string, h http.Handler) http.Handler {
|
||||||
|
if prefix == "" {
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if prefix != "/" && r.URL.Path == "/" {
|
||||||
|
http.Redirect(w, r, prefix+"/", http.StatusFound)
|
||||||
|
} else if p := strings.TrimPrefix(r.URL.Path, prefix); len(p) < len(r.URL.Path) {
|
||||||
|
r2 := new(http.Request)
|
||||||
|
*r2 = *r
|
||||||
|
r2.URL = new(url.URL)
|
||||||
|
*r2.URL = *r.URL
|
||||||
|
r2.URL.Path = p
|
||||||
|
h.ServeHTTP(ResponseWriterPrefix{w, prefix}, r2)
|
||||||
|
} else {
|
||||||
|
h.ServeHTTP(w, r)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
var bind = flag.String("bind", ":8081", "Bind port/socket")
|
||||||
|
var dsn = flag.String("dsn", DSNGenerator(), "DSN to connect to the MySQL server")
|
||||||
|
var baseURL = flag.String("baseurl", "/", "URL prepended to each URL")
|
||||||
|
flag.StringVar(&StaticDir, "static", "./htdocs/", "Directory containing static files")
|
||||||
|
flag.StringVar(&sharedSecret, "sharedsecret", "adelina", "secret used to communicate with remote validator")
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
// Sanitize options
|
||||||
|
var err error
|
||||||
|
log.Println("Checking paths...")
|
||||||
|
if StaticDir, err = filepath.Abs(StaticDir); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
if *baseURL != "/" {
|
||||||
|
tmp := path.Clean(*baseURL)
|
||||||
|
baseURL = &tmp
|
||||||
|
} else {
|
||||||
|
tmp := ""
|
||||||
|
baseURL = &tmp
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize contents
|
||||||
|
log.Println("Opening database...")
|
||||||
|
if err := DBInit(*dsn); err != nil {
|
||||||
|
log.Fatal("Cannot open the database: ", err)
|
||||||
|
}
|
||||||
|
defer DBClose()
|
||||||
|
|
||||||
|
log.Println("Creating database...")
|
||||||
|
if err := DBCreate(); err != nil {
|
||||||
|
log.Fatal("Cannot create database: ", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Serve content
|
||||||
|
log.Println("Ready, listening on", *bind)
|
||||||
|
if err := http.ListenAndServe(*bind, StripPrefix(*baseURL, Router())); err != nil {
|
||||||
|
log.Fatal("Unable to listen and serve: ", err)
|
||||||
|
}
|
||||||
|
}
|
29
token-validator/static.go
Normal file
29
token-validator/static.go
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"path"
|
||||||
|
|
||||||
|
"github.com/julienschmidt/httprouter"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
router.GET("/", func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
||||||
|
http.ServeFile(w, r, path.Join(StaticDir, "index.html"))
|
||||||
|
})
|
||||||
|
router.GET("/css/*_", func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
||||||
|
http.ServeFile(w, r, path.Join(StaticDir, r.URL.Path))
|
||||||
|
})
|
||||||
|
router.GET("/fonts/*_", func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
||||||
|
http.ServeFile(w, r, path.Join(StaticDir, r.URL.Path))
|
||||||
|
})
|
||||||
|
router.GET("/img/*_", func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
||||||
|
http.ServeFile(w, r, path.Join(StaticDir, r.URL.Path))
|
||||||
|
})
|
||||||
|
router.GET("/js/*_", func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
||||||
|
http.ServeFile(w, r, path.Join(StaticDir, r.URL.Path))
|
||||||
|
})
|
||||||
|
router.GET("/views/*_", func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
||||||
|
http.ServeFile(w, r, path.Join(StaticDir, r.URL.Path))
|
||||||
|
})
|
||||||
|
}
|
99
token-validator/students.go
Normal file
99
token-validator/students.go
Normal file
|
@ -0,0 +1,99 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/julienschmidt/httprouter"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
router.GET("/api/students/", apiHandler(
|
||||||
|
func(httprouter.Params,[]byte) (interface{}, error) {
|
||||||
|
return getStudents() }))
|
||||||
|
router.POST("/api/students/", apiHandler(createStudent))
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
type Student struct {
|
||||||
|
Id int64 `json:"id"`
|
||||||
|
Login string `json:"login"`
|
||||||
|
Time time.Time `json:"time"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func getStudents() (students []Student, err error) {
|
||||||
|
if rows, errr := DBQuery("SELECT id_student, login, time FROM students"); errr != nil {
|
||||||
|
return nil, errr
|
||||||
|
} else {
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
for rows.Next() {
|
||||||
|
var s Student
|
||||||
|
if err = rows.Scan(&s.Id, &s.Login, &s.Time); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
students = append(students, s)
|
||||||
|
}
|
||||||
|
if err = rows.Err(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getStudent(id int64) (s Student, err error) {
|
||||||
|
err = DBQueryRow("SELECT id_student, login, time FROM students WHERE id_student=?", id).Scan(&s.Id, &s.Login, &s.Time)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewStudent(login string) (Student, error) {
|
||||||
|
if res, err := DBExec("INSERT INTO students (login, time) VALUES (?, ?)", login, time.Now()); err != nil {
|
||||||
|
return Student{}, err
|
||||||
|
} else if sid, err := res.LastInsertId(); err != nil {
|
||||||
|
return Student{}, err
|
||||||
|
} else {
|
||||||
|
return Student{sid, login, time.Now()}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Student) Update() (int64, error) {
|
||||||
|
if res, err := DBExec("UPDATE students SET login = ?, time = ? WHERE id_student = ?", s.Login, s.Time, s.Id); err != nil {
|
||||||
|
return 0, err
|
||||||
|
} else if nb, err := res.RowsAffected(); err != nil {
|
||||||
|
return 0, err
|
||||||
|
} else {
|
||||||
|
return nb, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Student) Delete() (int64, error) {
|
||||||
|
if res, err := DBExec("DELETE FROM students WHERE id_student = ?", s.Id); err != nil {
|
||||||
|
return 0, err
|
||||||
|
} else if nb, err := res.RowsAffected(); err != nil {
|
||||||
|
return 0, err
|
||||||
|
} else {
|
||||||
|
return nb, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ClearStudents() (int64, error) {
|
||||||
|
if res, err := DBExec("DELETE FROM students"); err != nil {
|
||||||
|
return 0, err
|
||||||
|
} else if nb, err := res.RowsAffected(); err != nil {
|
||||||
|
return 0, err
|
||||||
|
} else {
|
||||||
|
return nb, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func createStudent(_ httprouter.Params, body []byte) (interface{}, error) {
|
||||||
|
var std Student
|
||||||
|
if err := json.Unmarshal(body, &std); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return NewStudent(strings.TrimSpace(std.Login))
|
||||||
|
}
|
Reference in New Issue
Block a user