An authentication server
您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

264 行
7.1 KiB

  1. require "kemal"
  2. require "../*"
  3. require "io"
  4. require "file"
  5. require "exception"
  6. require "crypto/bcrypt/password"
  7. require "uuid"
  8. require "uuid/json"
  9. require "socket/udp_socket"
  10. require "../../config"
  11. def fread(file) : String
  12. slc = Bytes.new file.size
  13. count = file.read slc
  14. String.new slc[0, count]
  15. end
  16. def authenticate(user : String, token : UUID) : (User | Nil)
  17. user_file : User | Nil = nil
  18. user_file = KVStore.access.fetch!("user/"+user)
  19. if user_file.nil?
  20. return nil
  21. end
  22. if nil == user_file.not_nil!.tokens.not_nil!.find{ |tok| token == tok}
  23. nil
  24. else
  25. user_file.not_nil!.password_hash = ""
  26. user_file
  27. end
  28. end
  29. def authenticate!(user : String, token : UUID) : User
  30. authenticate(user, token).not_nil!
  31. end
  32. def authenticate_admin!(user : String, token : UUID) : User
  33. user = authenticate(user, token).not_nil!
  34. if(user.type==UserType::Administrator)
  35. return user
  36. end
  37. raise "Administrator only"
  38. end
  39. ws "/socket/user/authenticate" do |socket|
  40. socket.on_message do |message|
  41. json = JSON.parse(message)
  42. if(authenticate(json["user"].to_s, UUID.new(json["api_token"].to_s)).nil?)
  43. json.as_h.["valid"]=JSON::Any.new false
  44. else
  45. json.as_h.["valid"]=JSON::Any.new true
  46. end
  47. socket.send json.to_json
  48. end
  49. end
  50. udp_listener = UDPSocket.new
  51. udp_listener.bind "0.0.0.0", 3000
  52. udp_mutex = Mutex.new
  53. (1..Statics.nb_udp_listeners).each do
  54. spawn do
  55. while true
  56. message : String | Nil = nil
  57. address : Socket::IPAddress | Nil = nil
  58. udp_mutex.synchronize do
  59. message, address = udp_listener.receive
  60. end
  61. json = JSON.parse(message.not_nil!)
  62. if(authenticate(json["user"].to_s, UUID.new(json["api_token"].to_s)).nil?)
  63. json.as_h.["valid"]=JSON::Any.new false
  64. else
  65. json.as_h.["valid"]=JSON::Any.new true
  66. end
  67. udp_mutex.synchronize do
  68. udp_listener.send json.to_json, address.not_nil!
  69. end
  70. end
  71. end
  72. end
  73. post "/login" do |context|
  74. user = User.from_json context.request.body.not_nil!
  75. user_file : User
  76. token : UUID | Nil = nil
  77. KVStore.access.transaction do |store|
  78. begin
  79. user_file = store.fetch! "user/"+user.email
  80. rescue ex
  81. halt context, status_code: 403, response: ex.to_s
  82. end
  83. if Crypto::Bcrypt::Password.new(user_file.password_hash.not_nil!) == user.password_hash.not_nil!
  84. else
  85. halt context, status_code: 403, response: "Invalid password"
  86. end
  87. if user_file.tokens.nil?
  88. user_file.tokens = Array(UUID).new
  89. end
  90. token = UUID.random()
  91. user_file.tokens.not_nil!<<token
  92. if user_file.tokens.not_nil!.size>5
  93. user_file.tokens = user_file.tokens.not_nil!.last(5)
  94. end
  95. store.push "user/"+user.email, user_file.to_json
  96. end
  97. context.response.content_type = "application/json"
  98. token.not_nil!.to_json
  99. end
  100. get "/logout" do |context|
  101. user = context.request.headers["user"]
  102. KVStore.access.transaction do |store|
  103. user_file = store.fetch!("user/"+user)
  104. user_file.tokens.not_nil!.delete(UUID.new(context.request.headers["api_token"]))
  105. store.push "user/"+user, user_file.to_json
  106. end
  107. context.response.content_type = "application/json"
  108. "OK".to_json
  109. end
  110. post "/logout-all" do |context|
  111. user : User
  112. begin
  113. user = authenticate!(context.request.headers["user"],UUID.new(context.request.headers["api_token"]))
  114. rescue ex
  115. halt context, status_code: 403, response: ex.to_s
  116. end
  117. KVStore.access.transaction do |store|
  118. user_file = store.fetch!("user/"+user.email)
  119. user_file.tokens=Array(UUID).new
  120. store.push "user/"+user_file.email, user_file.to_json
  121. end
  122. context.response.content_type = "application/json"
  123. "OK".to_json
  124. end
  125. post "/user" do |context|
  126. user = User.from_json context.request.body.not_nil!
  127. ph = user.password_hash
  128. user.tokens = Array(UUID).new
  129. user.invoices = Array(Invoice).new
  130. if ph.nil?
  131. halt context, status_code: 401, response: "No password provided"
  132. else
  133. user.password_hash=Crypto::Bcrypt::Password.create(ph,cost: 12).to_s
  134. end
  135. if Statics.email_regex.match(user.email)==nil
  136. halt context, status_code: 401, response: "Bad email address provided"
  137. end
  138. begin
  139. KVStore.access.transaction do |store|
  140. if store.fetch("user/"+user.email)!=nil
  141. raise IndexError.new
  142. end
  143. store.push "user/"+user.email, user.to_json
  144. end
  145. rescue ex
  146. halt context, status_code: 401, response: "Email address already in use"
  147. end
  148. context.response.content_type = "application/json"
  149. "OK".to_json
  150. end
  151. get "/user/tokens" do |context|
  152. user : User
  153. begin
  154. user = authenticate!(context.request.headers["user"],UUID.new(context.request.headers["api_token"]))
  155. rescue ex
  156. halt context, status_code: 403, response: ex.to_s
  157. end
  158. context.response.content_type = "application/json"
  159. user.tokens.to_json
  160. end
  161. get "/user/authenticate" do |context|
  162. user : User
  163. begin
  164. user = authenticate!(context.request.headers["user"],UUID.new(context.request.headers["api_token"]))
  165. rescue ex
  166. halt context, status_code: 403, response: ex.to_s
  167. end
  168. context.response.content_type = "application/json"
  169. "OK".to_json
  170. end
  171. get "/user/address" do |context|
  172. user : User
  173. begin
  174. user = authenticate!(context.request.headers["user"],UUID.new(context.request.headers["api_token"]))
  175. rescue ex
  176. halt context, status_code: 403, response: ex.to_s
  177. end
  178. context.response.content_type = "application/json"
  179. user.addresses.to_json
  180. end
  181. post "/user/address" do |context|
  182. user : User
  183. begin
  184. user = authenticate!(context.request.headers["user"],UUID.new(context.request.headers["api_token"]))
  185. rescue ex
  186. halt context, status_code: 403, response: ex.to_s
  187. end
  188. addresses = Array(Address).from_json(context.request.body.not_nil!).not_nil!
  189. KVStore.access.transaction do |store|
  190. user_file = store.fetch!("user/"+user.email)
  191. old_list=user_file.addresses
  192. if old_list.nil?
  193. else
  194. addresses=old_list+addresses
  195. end
  196. user_file.addresses=addresses
  197. store.push "user/"+user_file.email, user_file.to_json
  198. end
  199. context.response.content_type = "application/json"
  200. "OK".to_json
  201. end
  202. delete "/user/address" do |context|
  203. user = authenticate!(context.request.headers["user"],UUID.new(context.request.headers["api_token"]))
  204. addresses = Array(Address).from_json(context.request.body.not_nil!).not_nil!
  205. KVStore.access.transaction do |store|
  206. user_file = store.fetch!("user/"+user.email)
  207. old_list=user_file.addresses
  208. if old_list.nil?
  209. addresses=Array(Address).new
  210. else
  211. addresses=old_list.select do |v|
  212. isin=false
  213. addresses.each do |va|
  214. isin |= v==va
  215. end
  216. !isin
  217. end
  218. end
  219. user_file.addresses=addresses
  220. store.push "user/"+user_file.email, user_file.to_json
  221. end
  222. context.response.content_type = "application/json"
  223. "OK".to_json
  224. end
  225. get "/user" do |context|
  226. context.response.content_type = "application/json"
  227. user : User | Nil
  228. begin
  229. user = authenticate!(context.request.headers["user"],UUID.new(context.request.headers["api_token"]))
  230. rescue ex
  231. resp = String::Builder.build do |builder|
  232. if ENV["KEMAL_ENV"] == "test"
  233. ex.inspect_with_backtrace builder
  234. else
  235. ex.to_s builder
  236. end
  237. end
  238. halt context, status_code: 403, response: resp
  239. end
  240. user.not_nil!
  241. end