PageRenderTime 53ms CodeModel.GetById 33ms RepoModel.GetById 1ms app.codeStats 0ms

/src/vafan/user.go

https://github.com/saulhoward/vafan
Go | 367 lines | 313 code | 35 blank | 19 comment | 74 complexity | 1a096102be3e09d9223f3ac5988e9102 MD5 | raw file
  1. // Vafan - a web server for Convict Films
  2. //
  3. // User
  4. //
  5. // @url http://saulhoward.com/vafan
  6. // @author saul@saulhoward.com
  7. //
  8. package vafan
  9. import (
  10. "code.google.com/p/gorilla/mux"
  11. "crypto/hmac"
  12. "crypto/rand"
  13. "crypto/sha1"
  14. "database/sql"
  15. "errors"
  16. "fmt"
  17. _ "github.com/ziutek/mymysql/godrv"
  18. "hash"
  19. "io"
  20. "net/http"
  21. "net/url"
  22. "regexp"
  23. "strings"
  24. )
  25. var ErrWrongPassword = errors.New("user: password fail")
  26. type user struct {
  27. ID string `json:"id"` // UUID v4 with dashes
  28. Username string `json:"username"`
  29. EmailAddress string `json:"emailAddress"`
  30. Role string `json:"role"`
  31. URL string `json:"url"`
  32. IsLoggedIn bool `json:"isLoggedIn"`
  33. passwordHash string
  34. salt string
  35. }
  36. // HTTP Resource methods
  37. func (u user) GetURL(req *http.Request, s *site) *url.URL {
  38. return makeURL(u, req, s, []string{"id", u.ID})
  39. }
  40. func (u user) ServeHTTP(w http.ResponseWriter, r *http.Request, reqU *user) {
  41. res := Resource{
  42. title: "User",
  43. description: "User page",
  44. }
  45. res.content = make(resourceContent)
  46. // check if user has permission? whose user page is this?
  47. vars := mux.Vars(r)
  48. u = *getUserById(vars["id"])
  49. if userIsSame(reqU, &u) || reqU.Role == "superadmin" {
  50. res.content["user"] = u
  51. res.write(w, r, &u, reqU)
  52. return
  53. }
  54. forbidden{}.ServeHTTP(w, r, reqU)
  55. return
  56. }
  57. // Other methods
  58. const defaultUserRole = "user"
  59. // brand new user, freshly minted id
  60. func NewUser() *user {
  61. u := user{ID: newUUID(), Role: defaultUserRole}
  62. return &u
  63. }
  64. // -- DB
  65. func connectSQLDB() *sql.DB {
  66. db, err := sql.Open("mymysql", fmt.Sprintf("vafan/%v/%v", vafanConf.mysql.user, vafanConf.mysql.password))
  67. if err != nil {
  68. panic("Error connecting to mysql db: " + err.Error())
  69. }
  70. return db
  71. }
  72. func createSalt() string {
  73. b := make([]byte, 16)
  74. _, err := io.ReadFull(rand.Reader, b)
  75. if err != nil {
  76. logger.Err(fmt.Sprintf("Failed to create random salt: %v", err))
  77. return "defaultSalt"
  78. }
  79. return string(b)
  80. }
  81. func hashPassword(password string, salt string) string {
  82. var h hash.Hash = hmac.New(sha1.New, []byte(salt))
  83. h.Write([]byte(password))
  84. return string(h.Sum(nil))
  85. }
  86. // -- User stuff
  87. func getUserByUsername(username string) (u *user) {
  88. u = NewUser()
  89. db := connectSQLDB()
  90. defer db.Close()
  91. selectUser, err := db.Prepare(`select id, username, emailAddress, role, passwordHash, salt from users where username=?`)
  92. if err != nil {
  93. logger.Err(fmt.Sprintf("Failed to prepare db (MySQL): %v", err))
  94. return
  95. }
  96. err = selectUser.QueryRow(username).Scan(&u.ID, &u.Username, &u.EmailAddress, &u.Role, &u.passwordHash, &u.salt)
  97. if err != nil {
  98. logger.Err(fmt.Sprintf("Failed to select user (MySQL): %v", err))
  99. return
  100. }
  101. return
  102. }
  103. func getUserByEmailAddress(emailAddress string) (u *user) {
  104. u = NewUser()
  105. db := connectSQLDB()
  106. defer db.Close()
  107. selectUser, err := db.Prepare(`select id, username, emailAddress, role, passwordHash, salt from users where emailAddress=?`)
  108. if err != nil {
  109. logger.Err(fmt.Sprintf("Failed to prepare db (MySQL): %v", err))
  110. return
  111. }
  112. err = selectUser.QueryRow(emailAddress).Scan(&u.ID, &u.Username, &u.EmailAddress, &u.Role, &u.passwordHash, &u.salt)
  113. if err != nil {
  114. logger.Err(fmt.Sprintf("Failed to select user (MySQL): %v", err))
  115. return
  116. }
  117. return
  118. }
  119. func getUserById(id string) (u *user) {
  120. u = NewUser()
  121. db := connectSQLDB()
  122. defer db.Close()
  123. selectUser, err := db.Prepare(`select id, username, emailAddress, role, passwordHash, salt from users where id=?`)
  124. if err != nil {
  125. logger.Err(fmt.Sprintf("Failed to prepare db (MySQL): %v", err))
  126. return
  127. }
  128. err = selectUser.QueryRow(id).Scan(&u.ID, &u.Username, &u.EmailAddress, &u.Role, &u.passwordHash, &u.salt)
  129. if err != nil {
  130. logger.Err(fmt.Sprintf("Failed to select user (MySQL): %v", err))
  131. return
  132. }
  133. return
  134. }
  135. // just wants an id, the simplest form of user
  136. func GetUser(id string) *user {
  137. u := user{ID: id, Role: defaultUserRole}
  138. return &u
  139. }
  140. // needs a map of user properties, id must be set
  141. func getUserForUserInfo(userInfo map[string]string) (u *user, err error) {
  142. if userInfo["Id"] == "" {
  143. err = errors.New("User: ID must be set")
  144. return
  145. }
  146. newU := user{
  147. ID: userInfo["Id"],
  148. Username: userInfo["Username"],
  149. EmailAddress: userInfo["EmailAddress"],
  150. Role: userInfo["Role"],
  151. }
  152. return &newU, err
  153. }
  154. // Use UUID v4 as user IDs
  155. func newUUID() string {
  156. b := make([]byte, 16)
  157. _, err := io.ReadFull(rand.Reader, b)
  158. if err != nil {
  159. logger.Err(fmt.Sprintf("Failed to create random uuid: %v", err))
  160. return "00000000-0000-0000-0000-000000000000"
  161. }
  162. b[6] = (b[6] & 0x0F) | 0x40
  163. b[8] = (b[8] &^ 0x40) | 0x80
  164. return fmt.Sprintf("%x-%x-%x-%x-%x", b[:4], b[4:6], b[6:8], b[8:10], b[10:])
  165. }
  166. func MakeUserAdmin(name string) (err error) {
  167. u := getUserByUsername(name)
  168. err = u.changeRole("superadmin")
  169. return
  170. }
  171. func (u *user) save(password string) error {
  172. u.salt = createSalt()
  173. u.passwordHash = hashPassword(password, u.salt)
  174. db := connectSQLDB()
  175. defer db.Close()
  176. query := `insert into users values (?, ?, ?, ?, ?, ?)`
  177. stmt, err := db.Prepare(query)
  178. if err != nil {
  179. logger.Err(fmt.Sprintf("Failed to prepare db (MySQL): %v", err))
  180. return err
  181. }
  182. _, err = stmt.Exec(u.ID, u.Username, u.EmailAddress, u.passwordHash, u.salt, u.Role)
  183. if err != nil {
  184. logger.Err(fmt.Sprintf("Failed to create user (MySQL): %v", err))
  185. return err
  186. }
  187. return nil
  188. }
  189. func (u *user) changeRole(role string) (err error) {
  190. db := connectSQLDB()
  191. defer db.Close()
  192. query := `update users set role=? where id=?`
  193. stmt, err := db.Prepare(query)
  194. if err != nil {
  195. logger.Err(fmt.Sprintf("Failed to prepare db (MySQL): %v", err))
  196. return
  197. }
  198. _, err = stmt.Exec(role, u.ID)
  199. if err != nil {
  200. logger.Err(fmt.Sprintf("Failed to change user role (MySQL): %v", err))
  201. return
  202. }
  203. return nil
  204. }
  205. func (u *user) isLegal(password string) bool {
  206. if !u.isUsernameLegal() ||
  207. !u.isEmailAddressLegal() ||
  208. !u.isPasswordLegal(password) {
  209. return false
  210. }
  211. return true
  212. }
  213. func (u *user) isUsernameLegal() bool {
  214. const illegalCharsRe = `[^\p{L}\p{N}]+`
  215. var re = regexp.MustCompile(illegalCharsRe)
  216. if re.MatchString(u.Username) {
  217. return false
  218. }
  219. return true
  220. }
  221. func (u *user) isRegistered() bool {
  222. db := connectSQLDB()
  223. defer db.Close()
  224. selectUser, err := db.Prepare(`select id from users where id=?`)
  225. if err != nil {
  226. logger.Err(fmt.Sprintf("Failed to prepare db (MySQL): %v", err))
  227. return false
  228. }
  229. var id int
  230. err = selectUser.QueryRow(u.ID).Scan(&id)
  231. if err != nil {
  232. if err == sql.ErrNoRows {
  233. return false
  234. } else {
  235. logger.Err(fmt.Sprintf(
  236. "Failed to select user (MySQL): %v", err))
  237. return false
  238. }
  239. }
  240. return true
  241. }
  242. func (u *user) isUsernameNew() bool {
  243. db := connectSQLDB()
  244. defer db.Close()
  245. selectUser, err := db.Prepare(
  246. `select username from users where username=?`)
  247. if err != nil {
  248. logger.Err(fmt.Sprintf(
  249. "Failed to prepare db (MySQL): %v", err))
  250. return false
  251. }
  252. var username string
  253. err = selectUser.QueryRow(u.Username).Scan(&username)
  254. if err != nil {
  255. if err == sql.ErrNoRows {
  256. return true
  257. } else {
  258. logger.Err(fmt.Sprintf(
  259. "Failed to select user (MySQL): %v", err))
  260. return false
  261. }
  262. }
  263. return false
  264. }
  265. func (u *user) isEmailAddressLegal() bool {
  266. if strings.Contains(u.EmailAddress, "@") {
  267. return true
  268. }
  269. return false
  270. }
  271. func (u *user) isEmailAddressNew() bool {
  272. db := connectSQLDB()
  273. defer db.Close()
  274. selectUser, err := db.Prepare(`select emailAddress from users where emailAddress=?`)
  275. if err != nil {
  276. logger.Err(fmt.Sprintf("Failed to prepare db (MySQL): %v", err))
  277. return false
  278. }
  279. var emailAddress string
  280. err = selectUser.QueryRow(u.EmailAddress).Scan(&emailAddress)
  281. if err != nil {
  282. if err == sql.ErrNoRows {
  283. return true
  284. } else {
  285. logger.Err(fmt.Sprintf("Failed to select user (MySQL): %v", err))
  286. return false
  287. }
  288. }
  289. return false
  290. }
  291. func (u *user) isPasswordLegal(password string) bool {
  292. if len(password) < 6 {
  293. return false
  294. }
  295. return true
  296. }
  297. func (u *user) isNew() bool {
  298. if u.isRegistered() || !u.isUsernameNew() || !u.isEmailAddressNew() {
  299. return false
  300. }
  301. return true
  302. }
  303. func (u *user) setLoggedIn() {
  304. u.IsLoggedIn = true
  305. }
  306. // Login
  307. func login(usernameOrEmailAddress string, password string) (u *user, err error) {
  308. // confirm username and or email exists, get user
  309. u = NewUser()
  310. err = nil
  311. u.Username = usernameOrEmailAddress
  312. if !u.isUsernameNew() {
  313. u = getUserByUsername(usernameOrEmailAddress)
  314. } else {
  315. u.EmailAddress = usernameOrEmailAddress
  316. if !u.isEmailAddressNew() {
  317. u = getUserByEmailAddress(usernameOrEmailAddress)
  318. }
  319. }
  320. // confirm that the user's password is correct
  321. if hashPassword(password, u.salt) == u.passwordHash {
  322. return
  323. }
  324. err = ErrWrongPassword
  325. return
  326. }
  327. func userIsSame(u1 *user, u2 *user) bool {
  328. if u1.ID == u2.ID {
  329. return true
  330. }
  331. return false
  332. }