unsakini 0.0.4.2 → 0.0.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (222) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +26 -0
  3. data/angular/angular-cli.json +5 -3
  4. data/angular/npm-debug.log +54 -0
  5. data/angular/package.json +4 -1
  6. data/angular/src/app/app.component.html +1 -4
  7. data/angular/src/app/app.module.ts +8 -7
  8. data/angular/src/app/app.routes.module.ts +12 -2
  9. data/angular/src/app/confirm-account/confirm-account.component.ts +27 -0
  10. data/angular/src/app/confirm-account/confirm-account.html +41 -0
  11. data/angular/src/app/confirm-account/confirm-account.module.ts +24 -0
  12. data/angular/src/app/confirm-account/confirm-account.scss +3 -0
  13. data/angular/src/app/confirm-account/confirm-account.service.ts +27 -0
  14. data/angular/src/app/confirm-account/index.ts +3 -0
  15. data/angular/src/app/index.ts +2 -0
  16. data/angular/src/app/login/index.ts +3 -0
  17. data/angular/src/app/login/login.component.ts +40 -0
  18. data/angular/src/app/login/login.html +43 -0
  19. data/angular/src/app/login/login.module.ts +27 -0
  20. data/angular/src/app/login/login.service.ts +48 -0
  21. data/angular/src/app/registration/index.ts +3 -0
  22. data/angular/src/app/registration/registration.component.html +70 -12
  23. data/angular/src/app/registration/registration.component.spec.ts +8 -11
  24. data/angular/src/app/registration/registration.component.ts +10 -8
  25. data/angular/src/app/registration/registration.module.ts +23 -0
  26. data/angular/src/app/registration/registration.service.ts +46 -0
  27. data/angular/src/app/registration/registration.services.spec.ts +71 -0
  28. data/angular/src/app/services/auth-http/auth.http.service.ts +35 -0
  29. data/angular/src/app/services/auth-http/index.ts +1 -0
  30. data/angular/src/app/services/http/http.service.spec.ts +205 -0
  31. data/angular/src/app/services/http/http.service.ts +40 -0
  32. data/angular/src/app/services/http/index.ts +1 -0
  33. data/angular/src/app/services/index.ts +3 -0
  34. data/angular/src/app/services/services.module.ts +33 -0
  35. data/angular/src/assets/global.scss +3 -0
  36. data/angular/src/environments/custom.ts +4 -0
  37. data/angular/src/environments/environment.prod.ts +2 -1
  38. data/angular/src/environments/environment.ts +2 -1
  39. data/angular/src/index.html +1 -1
  40. data/app/controllers/application_controller.rb +2 -2
  41. data/app/controllers/concerns/unsakini/board_owner_controller_concern.rb +42 -0
  42. data/app/controllers/concerns/unsakini/comment_owner_controller_concern.rb +36 -0
  43. data/app/controllers/concerns/unsakini/logged_in_controller_concern.rb +23 -0
  44. data/app/controllers/concerns/unsakini/post_owner_controller_concern.rb +38 -0
  45. data/app/controllers/concerns/unsakini/serializer_controller_concern.rb +13 -0
  46. data/app/controllers/unsakini/base_controller.rb +6 -0
  47. data/app/controllers/unsakini/boards_controller.rb +76 -0
  48. data/app/controllers/unsakini/comments_controller.rb +54 -0
  49. data/app/controllers/unsakini/posts_controller.rb +61 -0
  50. data/app/controllers/unsakini/share_board_controller.rb +122 -0
  51. data/app/controllers/unsakini/user_token_controller.rb +17 -0
  52. data/app/controllers/unsakini/users_controller.rb +69 -0
  53. data/app/controllers/unsakini/web_controller.rb +27 -0
  54. data/app/mailers/unsakini/user_mailer.rb +13 -0
  55. data/app/models/concerns/unsakini/encryptable_model_concern.rb +97 -0
  56. data/app/models/unsakini/application_record.rb +7 -0
  57. data/app/models/unsakini/board.rb +16 -0
  58. data/app/models/unsakini/comment.rb +12 -0
  59. data/app/models/unsakini/post.rb +15 -0
  60. data/app/models/unsakini/user.rb +43 -0
  61. data/app/models/unsakini/user_board.rb +84 -0
  62. data/app/models/unsakini.rb +5 -0
  63. data/app/serializers/unsakini/board_serializer.rb +7 -0
  64. data/app/serializers/{comment_serializer.rb → unsakini/comment_serializer.rb} +6 -3
  65. data/app/serializers/unsakini/post_serializer.rb +26 -0
  66. data/app/serializers/unsakini/user_board_serializer.rb +14 -0
  67. data/app/serializers/{user_serializer.rb → unsakini/user_serializer.rb} +5 -2
  68. data/app/views/unsakini/user_mailer/confirm_account.html.erb +3 -0
  69. data/app/views/{web_base → unsakini/web}/index.html.erb +9 -7
  70. data/config/routes.rb +10 -10
  71. data/db/migrate/20161116114222_create_unsakini_boards.rb +10 -0
  72. data/db/migrate/{20161116200034_create_user_boards.rb → 20161116200034_create_unsakini_user_boards.rb} +3 -2
  73. data/db/migrate/{20161118031023_create_posts.rb → 20161118031023_create_unsakini_posts.rb} +2 -2
  74. data/db/migrate/{20161118100454_create_comments.rb → 20161118100454_create_unsakini_comments.rb} +2 -2
  75. data/db/migrate/20161126145352_create_unsakini_users.rb +15 -0
  76. data/lib/generators/unsakini/config/config_generator.rb +3 -1
  77. data/lib/generators/unsakini/dependencies/USAGE +5 -0
  78. data/lib/generators/unsakini/dependencies/dependencies_generator.rb +19 -0
  79. data/lib/tasks/unsakini_tasks.rake +6 -37
  80. data/lib/unsakini/engine.rb +2 -0
  81. data/lib/unsakini/version.rb +1 -1
  82. data/public/images/unsakini.svg +56 -0
  83. data/public/unsakini/app/448c34a56d699c29117adc64c43affeb.woff2 +0 -0
  84. data/public/unsakini/app/89889688147bd7575d6327160d64e760.svg +288 -0
  85. data/public/unsakini/app/assets/global.scss +3 -0
  86. data/public/unsakini/app/e18bbf611f2a2e43afc071aa2f4e1512.ttf +0 -0
  87. data/public/unsakini/app/f4769f9bdb7466be65088239c12046d1.eot +0 -0
  88. data/public/unsakini/app/fa2772327f55d8198301fdb8bcfc8158.woff +0 -0
  89. data/{angular/dist → public/unsakini/app}/favicon.ico +0 -0
  90. data/public/unsakini/app/index.html +14 -0
  91. data/public/unsakini/app/inline.d41d8cd98f00b204e980.bundle.js +2 -0
  92. data/public/unsakini/app/inline.d41d8cd98f00b204e980.bundle.map +1 -0
  93. data/public/unsakini/app/main.54f49c65d3d20650a5d5.bundle.js +2152 -0
  94. data/public/unsakini/app/main.54f49c65d3d20650a5d5.bundle.js.gz +0 -0
  95. data/public/unsakini/app/main.54f49c65d3d20650a5d5.bundle.map +1 -0
  96. data/public/unsakini/app/styles.58e065928ed8ebd0b582.bundle.js +2 -0
  97. data/public/unsakini/app/styles.58e065928ed8ebd0b582.bundle.map +1 -0
  98. data/public/unsakini/app/styles.5dac0e986fce6f8738b300cb558b56a0.bundle.css +8 -0
  99. data/spec/concerns/models/encryptable_concern.rb +3 -2
  100. data/spec/controllers/{web_base_controller_spec.rb → web_controller_spec.rb} +4 -3
  101. data/spec/dummy/config/application.rb +3 -1
  102. data/spec/dummy/config/environments/development.rb +2 -0
  103. data/spec/dummy/config/initializers/knock.rb +59 -0
  104. data/spec/dummy/db/schema.rb +16 -14
  105. data/spec/dummy/db/test.sqlite3 +0 -0
  106. data/spec/factories/boards.rb +1 -1
  107. data/spec/factories/comments.rb +1 -1
  108. data/spec/factories/posts.rb +1 -1
  109. data/spec/factories/user_boards.rb +1 -1
  110. data/spec/factories/users.rb +1 -1
  111. data/spec/models/board_spec.rb +2 -2
  112. data/spec/models/comment_spec.rb +2 -2
  113. data/spec/models/post_spec.rb +2 -2
  114. data/spec/models/user_board_spec.rb +19 -19
  115. data/spec/models/user_spec.rb +1 -1
  116. data/spec/requests/{api/boards/api_boards_crud_spec.rb → boards/boards_crud_spec.rb} +26 -26
  117. data/spec/requests/{api/boards/api_boards_pagination_spec.rb → boards/boards_pagination_spec.rb} +7 -7
  118. data/spec/requests/{api/boards/api_private_board_spec.rb → boards/private_board_spec.rb} +26 -26
  119. data/spec/requests/{api/boards/api_shared_board_spec.rb → boards/shared_board_spec.rb} +9 -9
  120. data/spec/requests/{api/boards/api_sharing_board_spec.rb → boards/sharing_board_spec.rb} +13 -13
  121. data/spec/requests/{api/comments/api_comments_pagination_spec.rb → comments/comments_pagination_spec.rb} +3 -3
  122. data/spec/requests/{api/comments/api_comments_private_board_spec.rb → comments/comments_private_board_spec.rb} +20 -20
  123. data/spec/requests/{api/comments/api_comments_shared_board_spec.rb → comments/comments_shared_board_spec.rb} +17 -17
  124. data/spec/requests/{api/posts/api_posts_pagination_spec.rb → posts/posts_pagination_spec.rb} +3 -3
  125. data/spec/requests/{api/posts/api_posts_private_board_spec.rb → posts/posts_private_board_spec.rb} +22 -22
  126. data/spec/requests/{api/posts/api_posts_shared_board_spec.rb → posts/posts_shared_board_spec.rb} +24 -24
  127. data/spec/requests/{api/user/api_user_create_spec.rb → user/user_create_spec.rb} +19 -23
  128. data/spec/requests/{api/user/api_user_search_spec.rb → user/user_search_spec.rb} +9 -9
  129. data/spec/schema/jwt.json +9 -0
  130. data/spec/spec_helper.rb +1 -0
  131. data/spec/support/auth_helper.rb +0 -2
  132. metadata +133 -200
  133. data/angular/dist/index.html +0 -14
  134. data/angular/dist/inline.bundle.js +0 -139
  135. data/angular/dist/inline.map +0 -1
  136. data/angular/dist/main.bundle.js +0 -64689
  137. data/angular/dist/main.map +0 -1
  138. data/angular/dist/styles.bundle.js +0 -364
  139. data/angular/dist/styles.map +0 -1
  140. data/angular/src/styles.css +0 -1
  141. data/app/controllers/api/boards_controller.rb +0 -73
  142. data/app/controllers/api/comments_controller.rb +0 -51
  143. data/app/controllers/api/posts_controller.rb +0 -58
  144. data/app/controllers/api/share_board_controller.rb +0 -118
  145. data/app/controllers/api/users_controller.rb +0 -40
  146. data/app/controllers/concerns/board_owner_controller_concern.rb +0 -38
  147. data/app/controllers/concerns/comment_owner_controller_concern.rb +0 -33
  148. data/app/controllers/concerns/logged_in_controller_concern.rb +0 -21
  149. data/app/controllers/concerns/post_owner_controller_concern.rb +0 -36
  150. data/app/controllers/concerns/serializer_controller_concern.rb +0 -11
  151. data/app/controllers/user_token_controller.rb +0 -2
  152. data/app/controllers/web_base_controller.rb +0 -23
  153. data/app/models/application_record.rb +0 -5
  154. data/app/models/board.rb +0 -14
  155. data/app/models/comment.rb +0 -9
  156. data/app/models/concerns/encryptable_model_concern.rb +0 -96
  157. data/app/models/post.rb +0 -12
  158. data/app/models/user.rb +0 -15
  159. data/app/models/user_board.rb +0 -82
  160. data/app/serializers/board_serializer.rb +0 -5
  161. data/app/serializers/post_serializer.rb +0 -23
  162. data/app/serializers/user_board_serializer.rb +0 -11
  163. data/db/migrate/20161116114222_create_boards.rb +0 -9
  164. data/db/migrate/20161118221508_add_encrypted_password_to_user_board.rb +0 -5
  165. data/db/migrate/20161122211105_create_users.rb +0 -12
  166. data/db/migrate/20161124102633_add_is_shared_to_boards.rb +0 -5
  167. data/lib/generators/unsakini/angular/USAGE +0 -8
  168. data/lib/generators/unsakini/angular/angular_generator.rb +0 -7
  169. data/public/images/logo.svg +0 -619
  170. data/spec/dummy/config/initializers/assets.rb +0 -11
  171. data/spec/dummy/config/initializers/cookies_serializer.rb +0 -5
  172. data/spec/dummy/config/initializers/session_store.rb +0 -3
  173. data/spec/dummy/db/development.sqlite3 +0 -0
  174. data/spec/dummy/db/migrate/20161124210219_create_boards.unsakini_engine.rb +0 -10
  175. data/spec/dummy/db/migrate/20161124210220_create_user_boards.unsakini_engine.rb +0 -12
  176. data/spec/dummy/db/migrate/20161124210221_create_posts.unsakini_engine.rb +0 -13
  177. data/spec/dummy/db/migrate/20161124210222_create_comments.unsakini_engine.rb +0 -12
  178. data/spec/dummy/db/migrate/20161124210223_add_encrypted_password_to_user_board.unsakini_engine.rb +0 -6
  179. data/spec/dummy/db/migrate/20161124210224_create_users.unsakini_engine.rb +0 -13
  180. data/spec/dummy/db/migrate/20161124210225_add_is_shared_to_boards.unsakini_engine.rb +0 -6
  181. data/spec/dummy/public/app/favicon.ico +0 -0
  182. data/spec/dummy/public/app/index.html +0 -14
  183. data/spec/dummy/public/app/inline.bundle.js +0 -139
  184. data/spec/dummy/public/app/inline.map +0 -1
  185. data/spec/dummy/public/app/main.bundle.js +0 -64689
  186. data/spec/dummy/public/app/main.map +0 -1
  187. data/spec/dummy/public/app/styles.bundle.js +0 -364
  188. data/spec/dummy/public/app/styles.map +0 -1
  189. data/spec/dummy/tmp/unsakini-ng2/LICENSE +0 -21
  190. data/spec/dummy/tmp/unsakini-ng2/README.md +0 -1
  191. data/spec/dummy/tmp/unsakini-ng2/angular-cli.json +0 -59
  192. data/spec/dummy/tmp/unsakini-ng2/e2e/app.e2e-spec.ts +0 -14
  193. data/spec/dummy/tmp/unsakini-ng2/e2e/app.po.ts +0 -11
  194. data/spec/dummy/tmp/unsakini-ng2/e2e/signup.e2e-spec.ts +0 -28
  195. data/spec/dummy/tmp/unsakini-ng2/e2e/signup.po.ts +0 -31
  196. data/spec/dummy/tmp/unsakini-ng2/e2e/tsconfig.json +0 -16
  197. data/spec/dummy/tmp/unsakini-ng2/karma.conf.js +0 -45
  198. data/spec/dummy/tmp/unsakini-ng2/package.json +0 -49
  199. data/spec/dummy/tmp/unsakini-ng2/protractor.conf.js +0 -32
  200. data/spec/dummy/tmp/unsakini-ng2/src/app/app.component.css +0 -0
  201. data/spec/dummy/tmp/unsakini-ng2/src/app/app.component.html +0 -4
  202. data/spec/dummy/tmp/unsakini-ng2/src/app/app.component.spec.ts +0 -47
  203. data/spec/dummy/tmp/unsakini-ng2/src/app/app.component.ts +0 -10
  204. data/spec/dummy/tmp/unsakini-ng2/src/app/app.module.ts +0 -29
  205. data/spec/dummy/tmp/unsakini-ng2/src/app/app.routes.module.ts +0 -29
  206. data/spec/dummy/tmp/unsakini-ng2/src/app/index.ts +0 -2
  207. data/spec/dummy/tmp/unsakini-ng2/src/app/registration/registration.component.css +0 -0
  208. data/spec/dummy/tmp/unsakini-ng2/src/app/registration/registration.component.html +0 -14
  209. data/spec/dummy/tmp/unsakini-ng2/src/app/registration/registration.component.spec.ts +0 -157
  210. data/spec/dummy/tmp/unsakini-ng2/src/app/registration/registration.component.ts +0 -42
  211. data/spec/dummy/tmp/unsakini-ng2/src/environments/environment.prod.ts +0 -3
  212. data/spec/dummy/tmp/unsakini-ng2/src/environments/environment.ts +0 -8
  213. data/spec/dummy/tmp/unsakini-ng2/src/favicon.ico +0 -0
  214. data/spec/dummy/tmp/unsakini-ng2/src/index.html +0 -14
  215. data/spec/dummy/tmp/unsakini-ng2/src/main.ts +0 -12
  216. data/spec/dummy/tmp/unsakini-ng2/src/polyfills.ts +0 -19
  217. data/spec/dummy/tmp/unsakini-ng2/src/styles.css +0 -1
  218. data/spec/dummy/tmp/unsakini-ng2/src/test.ts +0 -31
  219. data/spec/dummy/tmp/unsakini-ng2/src/tsconfig.json +0 -18
  220. data/spec/dummy/tmp/unsakini-ng2/src/typings.d.ts +0 -2
  221. data/spec/dummy/tmp/unsakini-ng2/tslint.json +0 -114
  222. data/spec/dummy/tmp/unsakini-ng2/typings.json +0 -4
@@ -0,0 +1,23 @@
1
+ # Ensures users are logged in and sets `@user` instance variable in the controllers.
2
+ # This is included in the base api controller.
3
+ #
4
+ # Returns `401` error if user is not authenticated
5
+ module Unsakini
6
+ module LoggedInControllerConcern
7
+ extend ActiveSupport::Concern
8
+
9
+ included do
10
+ include Knock::Authenticable
11
+ before_action :ensure_logged_in
12
+ end
13
+
14
+ private
15
+
16
+ def ensure_logged_in
17
+ authenticate_for Unsakini::User
18
+ @user = current_unsakini_user
19
+ head :unauthorized unless @user
20
+ end
21
+
22
+ end
23
+ end
@@ -0,0 +1,38 @@
1
+ # Ensures user is owner of the post and sets the `@post` variable in the controllers
2
+ module Unsakini
3
+ module PostOwnerControllerConcern
4
+ extend ActiveSupport::Concern
5
+
6
+ # Ensures user is owner of the post and sets the `@post` variable in the controllers
7
+ def ensure_post
8
+ post_id = params[:post_id] || params[:id]
9
+ board_id = params[:board_id]
10
+ result = has_post_access(board_id, post_id)
11
+ status = result[:status]
12
+ @post = result[:post]
13
+ head status if status != :ok
14
+ end
15
+
16
+ # Validate if user has access to the post in the board
17
+ #
18
+ # @param board_id [Integer] board id
19
+ # @param post_id [Integer] post id
20
+ def has_post_access(board_id, post_id)
21
+ post = Unsakini::Post.where(id: post_id, board_id: board_id)
22
+ .joins("LEFT JOIN #{UserBoard.table_name} ON #{UserBoard.table_name}.board_id = #{Post.table_name}.board_id")
23
+ .where("#{UserBoard.table_name}.user_id = ?", @user.id)
24
+ .first
25
+ if post.nil?
26
+ return {status: :forbidden}
27
+ else
28
+ return {status: :ok, post: post}
29
+ end
30
+ end
31
+
32
+ # Ensures user is owner of the post. Must be run after {#ensure_post}`.
33
+ def ensure_post_owner
34
+ render json: {}, status: :forbidden if @post.user_id != @user.id
35
+ end
36
+
37
+ end
38
+ end
@@ -0,0 +1,13 @@
1
+ module Unsakini
2
+ module SerializerControllerConcern
3
+
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ include ::ActionController::Serialization
8
+ end
9
+
10
+
11
+ end
12
+
13
+ end
@@ -0,0 +1,6 @@
1
+ # Base controller for API controllers
2
+ module Unsakini
3
+ class BaseController < ActionController::API
4
+
5
+ end
6
+ end
@@ -0,0 +1,76 @@
1
+
2
+ module Unsakini
3
+ class BoardsController < BaseController
4
+
5
+ include LoggedInControllerConcern
6
+ include SerializerControllerConcern
7
+ include BoardOwnerControllerConcern
8
+ include ::ActionController::Serialization
9
+
10
+ before_action :ensure_board, :only => [:show, :update, :destroy]
11
+ before_action :ensure_board_owner, :only => [:update, :destroy]
12
+
13
+ # Returns boards belonging to current user
14
+ #
15
+ # `GET /api/boards`
16
+ #
17
+ def index
18
+ admin = true
19
+ shared = false
20
+ admin = params[:is_admin] == 'true' if params[:admin]
21
+ shared = params[:shared] == 'true' if params[:shared]
22
+ result = @user.user_boards.shared(shared)
23
+ result = result.admin if admin
24
+ paginate json: result, per_page: 10
25
+ end
26
+
27
+ # Creates board belonging to current user.
28
+ #
29
+ # `POST /api/boards`
30
+ #
31
+ def create
32
+ @user_board = UserBoard.new(
33
+ name: params[:board][:name],
34
+ user_id: @user.id,
35
+ encrypted_password: params[:encrypted_password],
36
+ is_admin: true
37
+ )
38
+ if @user_board.save
39
+ render json: @user_board, status: :created
40
+ else
41
+ render json: @user_board.errors.full_messages, status: 422
42
+ end
43
+
44
+ end
45
+
46
+ # Render a single board.
47
+ #
48
+ # `GET /api/boards/:id`
49
+ #
50
+ def show
51
+ render json: @user_board
52
+ end
53
+
54
+ # Updates a single board.
55
+ #
56
+ # `PUT /api/boards/:id`
57
+ def update
58
+ if @user_board.update(name: params[:board][:name], encrypted_password: params[:encrypted_password])
59
+ render json: @user_board
60
+ else
61
+ errors = @board.errors.full_messages.concat @user_board.errors.full_messages
62
+ render json: errors, status: 422
63
+ end
64
+ end
65
+
66
+ # Deletes a board resource.
67
+ #
68
+ # `DELETE /api/boards/:id`
69
+ def destroy
70
+ @board.destroy
71
+ render json: {}, status: :ok
72
+ end
73
+
74
+ end
75
+
76
+ end
@@ -0,0 +1,54 @@
1
+ module Unsakini
2
+ class CommentsController < BaseController
3
+
4
+ include LoggedInControllerConcern
5
+ include PostOwnerControllerConcern
6
+ include CommentOwnerControllerConcern
7
+ include ::ActionController::Serialization
8
+
9
+ before_action :ensure_post, only: [:index, :create]
10
+ before_action :ensure_comment, only: [:show, :update, :destroy]
11
+ before_action :ensure_comment_owner, only: [:update, :destroy]
12
+
13
+ # Renders the comments belonging to the post
14
+ #
15
+ # `GET /api/boards/:board_id/posts/:post_id/`
16
+ def index
17
+ paginate json: @post.comments.page(params[:page]), per_page: 20
18
+ end
19
+
20
+ # Creates new comment belonging to the post
21
+ #
22
+ # `POST /api/boards/:board_id/posts/:post_id/`
23
+ def create
24
+ @comment = Comment.new(params.permit(:content))
25
+ @comment.user = @user
26
+ @comment.post = @post
27
+ if @comment.save
28
+ render json: @comment
29
+ else
30
+ render json: @comment.errors, status: 422
31
+ end
32
+ end
33
+
34
+ # Updates a comment
35
+ #
36
+ # `PUT /api/boards/:board_id/posts/:post_id/comments/:id`
37
+ def update
38
+ if @comment.update(params.permit(:content))
39
+ render json: @comment
40
+ else
41
+ render json: @comment.errors, status: 422
42
+ end
43
+ end
44
+
45
+ # Deletes a comment
46
+ #
47
+ # `DELETE /api/boards/:board_id/posts/:post_id/comments/:id`
48
+ def destroy
49
+ @comment.destroy
50
+ render json: {}, status: :ok
51
+ end
52
+ end
53
+
54
+ end
@@ -0,0 +1,61 @@
1
+ module Unsakini
2
+ class PostsController < BaseController
3
+
4
+ include LoggedInControllerConcern
5
+ include BoardOwnerControllerConcern
6
+ include PostOwnerControllerConcern
7
+ include ::ActionController::Serialization
8
+
9
+ before_action :ensure_board, only: [:index, :create]
10
+ before_action :ensure_post, only: [:show, :update, :destroy]
11
+ before_action :ensure_post_owner, only: [:update, :destroy]
12
+
13
+ # Renders the post belonging to the board
14
+ #
15
+ # `GET /api/boards/:board_id/posts`
16
+ def index
17
+ paginate json: @board.posts.page(params[:page]), per_page: 20
18
+ end
19
+
20
+ # Renders a single post belonging to the board
21
+ #
22
+ # `GET /api/boards/:board_id/posts/:id`
23
+ def show
24
+ render json: @post
25
+ end
26
+
27
+ # Creates a single post belonging to the board
28
+ #
29
+ # `POST /api/boards/:board_id/posts/`
30
+ def create
31
+ @post = Post.new(params.permit(:title, :content, :board_id))
32
+ @post.user = @user
33
+ if (@post.save)
34
+ render json: @post, status: :created
35
+ else
36
+ render json: @post.errors, status: 422
37
+ end
38
+ end
39
+
40
+ # Updates a single post belonging to the board
41
+ #
42
+ # `PUT /api/boards/:board_id/posts/:id`
43
+ def update
44
+ if (@post.update(params.permit(:title, :content)))
45
+ render json: @post, status: :ok
46
+ else
47
+ render json: @post.errors, status: 422
48
+ end
49
+ end
50
+
51
+ # Deletes a single post belonging to the board
52
+ #
53
+ # `DELETE /api/boards/:board_id/posts/:id`
54
+ def destroy
55
+ @post.destroy
56
+ render json: {}, status: :ok
57
+ end
58
+
59
+ end
60
+
61
+ end
@@ -0,0 +1,122 @@
1
+ module Unsakini
2
+
3
+ class ShareBoardController < BaseController
4
+
5
+ include LoggedInControllerConcern
6
+ include BoardOwnerControllerConcern
7
+ include PostOwnerControllerConcern
8
+ include CommentOwnerControllerConcern
9
+
10
+ before_action :validate_params
11
+
12
+ # Shares a board to other users. Example payload param:
13
+ #
14
+ # `POST /api/share/board`
15
+ #
16
+ # ```
17
+ # {
18
+ # board: {
19
+ # id: 1,
20
+ # name: 'some encrypted text',
21
+ # },
22
+ # posts: [
23
+ # {
24
+ # board_id: 1,
25
+ # title: 'some encrypted text',
26
+ # content: 'some encrypted text',
27
+ # comments: [
28
+ # {
29
+ # id: 1,
30
+ # content: 'some encrypted text',
31
+ # user_id: 1,
32
+ # post_id: 1,
33
+ # }
34
+ # ]
35
+ # }
36
+ # ],
37
+ # shared_user_ids: [1, 2, 3, 4],
38
+ # encrypted_password: 'some encrypted password'
39
+ # }
40
+ # ```
41
+ # The `encrypted_password` param will be used to decrypt contents of this board. The encryption happens in the client so
42
+ # the server don't really know what is the original password. The board creator will have to share it privately to other users whom he/she shared it with so they can access the board.
43
+ #
44
+ # `posts` and `comments` fields can be empty.
45
+ def index
46
+ ActiveRecord::Base.transaction do
47
+ if params[:posts]
48
+ params[:posts].each do |post|
49
+ p = Post.find(post[:id])
50
+ p.title = post[:title]
51
+ p.content = post[:content]
52
+ p.save!
53
+
54
+ if post[:comments] and p.valid?
55
+ post[:comments].each do |comment|
56
+ c = Comment.find(comment[:id])
57
+ c.content = comment[:content]
58
+ c.save!
59
+ end
60
+ end
61
+ end
62
+ end
63
+ if @user_board.share(params[:shared_user_ids], params[:encrypted_password])
64
+ render json: {}, status: :ok
65
+ else
66
+ raise "An error occured"
67
+ end
68
+ end
69
+ rescue
70
+ # clean up the created {UserBoard}s
71
+ render json: ["Some of the data can't be saved."], status: 422
72
+ end
73
+
74
+ # Validates the contents of params against the database records.
75
+ def validate_params
76
+
77
+ if params[:encrypted_password].nil? or params[:shared_user_ids].nil? or params[:board].nil?
78
+ render json: {}, status: 422
79
+ return
80
+ end
81
+
82
+ result = has_board_access(params[:board][:id])
83
+ if result[:status] != :ok
84
+ render json: {}, status: result[:status]
85
+ return
86
+ else
87
+ if !result[:user_board].is_admin
88
+ render json: {}, status: :forbidden
89
+ return
90
+ end
91
+ @board = result[:board]
92
+ @user_board = result[:user_board]
93
+ end
94
+
95
+ if params[:posts]
96
+
97
+ params[:posts].each do |post|
98
+ s = has_post_access(params[:board][:id], post[:id])[:status]
99
+ if s != :ok
100
+ render json: {}, status: s
101
+ return
102
+ end
103
+
104
+ if post[:comments]
105
+ post[:comments].each do |comment|
106
+ s = has_comment_access(post[:id], comment[:id])[:status]
107
+ if s != :ok
108
+ render json: {}, status: s
109
+ return
110
+ end
111
+ end
112
+ end
113
+
114
+ end
115
+
116
+ end
117
+
118
+ end
119
+
120
+ end
121
+
122
+ end
@@ -0,0 +1,17 @@
1
+ module Unsakini
2
+ class UserTokenController < Knock::AuthTokenController
3
+ def create
4
+ if entity.confirmed_at?
5
+ render json: auth_token, status: :created
6
+ else
7
+ res = {message: "Your account needs confirmation. Please follow the confirmation instructions sent to #{auth_params[:email]}"}
8
+ render status: 401, json: res
9
+ end
10
+ end
11
+
12
+ def entity_name
13
+ self.class.name.split('TokenController').first
14
+ end
15
+
16
+ end
17
+ end
@@ -0,0 +1,69 @@
1
+ module Unsakini
2
+
3
+ class UsersController < BaseController
4
+
5
+ include LoggedInControllerConcern
6
+ include ::ActionController::Serialization
7
+
8
+ skip_before_action :ensure_logged_in, only: [:create, :confirm]
9
+
10
+ #Creates a new user
11
+ def create
12
+ user = User.new(user_params)
13
+
14
+ if user.save
15
+ UserMailer.confirm_account(user).deliver_now
16
+ render json: user, status: :created
17
+ else
18
+ render json: user.errors, status: 422
19
+ end
20
+ end
21
+
22
+ # confirm user account
23
+ def confirm
24
+ token = params[:token].to_s
25
+
26
+ user = User.find_by(confirmation_token: token)
27
+
28
+ if user.present? && user.confirmation_token_valid?
29
+ if user.mark_as_confirmed!
30
+ render json: {status: 'User confirmed successfully'}, status: :ok
31
+ else
32
+ render json: user.errors, status: 422
33
+ end
34
+ else
35
+ render json: ['Invalid token'], status: :not_found
36
+ end
37
+ end
38
+
39
+ # Renders the current user as json
40
+ #
41
+ # `GET /api/user/:id`
42
+ #
43
+ def show
44
+ render json: @user
45
+ end
46
+
47
+ # Returns the user with matching email
48
+ #
49
+ # `GET /api/users/search?email=xxx`
50
+ #
51
+ def search
52
+ user = User.where("email = ? AND id != ?", params[:email], @user.id).first
53
+ if user
54
+ render json: user
55
+ else
56
+ render json: {}, status: :not_found
57
+ end
58
+ end
59
+
60
+ private
61
+
62
+ def user_params
63
+ params.permit(:name, :email, :password, :password_confirmation)
64
+ end
65
+
66
+ end
67
+
68
+
69
+ end
@@ -0,0 +1,27 @@
1
+ # Base controller for web pages
2
+
3
+ module Unsakini
4
+ class WebController < ActionController::Base
5
+ include ActionController::ImplicitRender
6
+ include ActionView::Layouts
7
+
8
+ # Renders welcome page
9
+ def index
10
+ @project = 'Unsakini'
11
+ @description = 'Privacy. Confidentiality. Security.'
12
+ @version = VERSION
13
+ @author = 'Adones Pitogo'
14
+ @repository = 'https://github.com/adonespitogo/unsakini'
15
+ @title = "#{@project} | #{@description}"
16
+ @tagline = "Created by and for online activists, information security enthusiasts and government surveillance evaders."
17
+ @keywords = "unsakini, encrypted, bulletin board, BB, ruby, rails"
18
+ end
19
+
20
+ # Renders the angular index view when request url is /app/* to enable html5 pushState capability of angularjs
21
+ def app
22
+ gem_root = File.expand_path '../../../..', __FILE__
23
+
24
+ render file: "#{gem_root}/public/unsakini/app/index.html", layout: false
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,13 @@
1
+ module Unsakini
2
+ class UserMailer < ::ActionMailer::Base
3
+ default from: 'notifications@example.com'
4
+
5
+ def confirm_account(user)
6
+ @user = user
7
+ @url = "#{root_url}app/account/confirm/#{@user.confirmation_token}"
8
+ mail(to: @user.email, subject: 'Unsakini - Account Confirmation')
9
+ end
10
+
11
+ end
12
+
13
+ end
@@ -0,0 +1,97 @@
1
+ require 'openssl'
2
+ require 'base64'
3
+
4
+ # Responsible for encryption and decryption of certain model attributes
5
+ module Unsakini
6
+ module EncryptableModelConcern
7
+ extend ActiveSupport::Concern
8
+
9
+ included do
10
+ before_save :encrypt_encryptable_attributes
11
+ after_save :decrypt_encryptable_attributes
12
+ after_find :decrypt_encryptable_attributes
13
+ end
14
+
15
+ module ClassMethods
16
+ # Sets the `encryptable_attributes` class instance variable in the model.
17
+ #
18
+ # Encryptable attributes are encrypted before saving using `before_save` hook and decrypted using `after_save` and `after_find` hooks.
19
+ #
20
+ # Example:
21
+ # ```
22
+ # class Board < BaseModel
23
+ # encryptable_attributes :name, :title, :content
24
+ # end
25
+ # ```
26
+ # @param attrs [Symbol] model attributes
27
+ #
28
+ def encryptable_attributes(*attrs)
29
+ @encryptable_attributes = attrs
30
+ end
31
+
32
+ end
33
+
34
+ # Returns the model's `encryptable_attributes` class instance variable.
35
+ #
36
+ def encryptable_attributes
37
+ self.class.instance_variable_get(:@encryptable_attributes) || []
38
+ end
39
+
40
+ private
41
+ # Encryptes the model's encryptable attributes before saving using Rails' `before_save` hook.
42
+ #
43
+ # **Note: Be careful in calling this method manually as it can corrupt the data.**
44
+ def encrypt_encryptable_attributes
45
+ encryptable_attributes.each do |k|
46
+ self[k] = encrypt(self[k])
47
+ end
48
+ end
49
+
50
+ # Decrypts the model's encryptable attributes using Rails' `after_save` and `after_find` hooks.
51
+ #
52
+ # **Note: Be careful in calling this method manually as it can corrupt the data.**
53
+ def decrypt_encryptable_attributes
54
+ encryptable_attributes.each do |k|
55
+ self[k] = decrypt(self[k])
56
+ end
57
+ end
58
+
59
+ # Determins if the value being encrypted/decryped is empty.
60
+ def is_empty_val(value)
61
+ !value or value.nil? or value == ""
62
+ end
63
+
64
+ # Returns the cipher algorithm used
65
+ def cipher
66
+ OpenSSL::Cipher::Cipher.new('aes-256-cbc')
67
+ end
68
+
69
+ # Returns the encryption key from the `unsakini_crypto_key` config
70
+ def cipher_key
71
+ begin
72
+ Rails.configuration.unsakini_crypto_key
73
+ rescue Exception => e
74
+ raise 'Encryption key is not set! Please run `rails g unsakini:config` before you proceed.'
75
+ end
76
+ end
77
+
78
+ # Encrypts model attribute value
79
+ def encrypt(value)
80
+ return value if is_empty_val(value)
81
+ c = cipher.encrypt
82
+ c.key = Digest::SHA256.digest(cipher_key)
83
+ c.iv = iv = c.random_iv
84
+ Base64.encode64(iv) + Base64.encode64(c.update(value.to_s) + c.final)
85
+ end
86
+
87
+ # Decrypts model attribute value
88
+ def decrypt(value)
89
+ return value if is_empty_val(value)
90
+ c = cipher.decrypt
91
+ c.key = Digest::SHA256.digest(cipher_key)
92
+ c.iv = Base64.decode64 value.slice!(0,25)
93
+ c.update(Base64.decode64(value.to_s)) + c.final
94
+ end
95
+
96
+ end
97
+ end
@@ -0,0 +1,7 @@
1
+ # Base application model
2
+ require_dependency 'unsakini'
3
+ module Unsakini
4
+ class ApplicationRecord < ActiveRecord::Base
5
+ self.abstract_class = true
6
+ end
7
+ end
@@ -0,0 +1,16 @@
1
+ #Board model
2
+ module Unsakini
3
+ class Board < ApplicationRecord
4
+
5
+ include EncryptableModelConcern
6
+ encryptable_attributes :name
7
+
8
+ validates :name, presence: true
9
+
10
+ has_many :users, through: :user_boards
11
+
12
+ has_many :user_boards, :dependent => :delete_all
13
+ has_many :posts, :dependent => :destroy
14
+
15
+ end
16
+ end
@@ -0,0 +1,12 @@
1
+ #Comment model
2
+ module Unsakini
3
+ class Comment < ApplicationRecord
4
+ include EncryptableModelConcern
5
+
6
+ encryptable_attributes :content
7
+ validates :content, presence: true
8
+
9
+ belongs_to :post
10
+ belongs_to :user
11
+ end
12
+ end
@@ -0,0 +1,15 @@
1
+ #Post model
2
+
3
+ module Unsakini
4
+ class Post < ApplicationRecord
5
+ include EncryptableModelConcern
6
+
7
+ encryptable_attributes :title, :content
8
+ validates :title, presence: true
9
+ validates :content, presence: true
10
+
11
+ belongs_to :user
12
+ belongs_to :board
13
+ has_many :comments, :dependent => :delete_all
14
+ end
15
+ end