masks 0.2.0
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.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +50 -0
- data/Rakefile +11 -0
- data/app/assets/builds/application.css +4764 -0
- data/app/assets/builds/application.js +8236 -0
- data/app/assets/builds/application.js.map +7 -0
- data/app/assets/builds/masks/application.css +1 -0
- data/app/assets/builds/masks/application.js +7122 -0
- data/app/assets/builds/masks/application.js.map +7 -0
- data/app/assets/images/masks.png +0 -0
- data/app/assets/javascripts/application.js +2 -0
- data/app/assets/javascripts/controllers/application.js +9 -0
- data/app/assets/javascripts/controllers/emails_controller.js +28 -0
- data/app/assets/javascripts/controllers/index.js +12 -0
- data/app/assets/javascripts/controllers/keys_controller.js +20 -0
- data/app/assets/javascripts/controllers/recover_controller.js +21 -0
- data/app/assets/javascripts/controllers/recover_password_controller.js +21 -0
- data/app/assets/javascripts/controllers/session_controller.js +94 -0
- data/app/assets/manifest.js +2 -0
- data/app/assets/masks_manifest.js +2 -0
- data/app/assets/stylesheets/application.css +26 -0
- data/app/controllers/concerns/masks/controller.rb +114 -0
- data/app/controllers/masks/actors_controller.rb +15 -0
- data/app/controllers/masks/application_controller.rb +35 -0
- data/app/controllers/masks/backup_codes_controller.rb +34 -0
- data/app/controllers/masks/debug_controller.rb +9 -0
- data/app/controllers/masks/devices_controller.rb +20 -0
- data/app/controllers/masks/emails_controller.rb +60 -0
- data/app/controllers/masks/error_controller.rb +14 -0
- data/app/controllers/masks/keys_controller.rb +45 -0
- data/app/controllers/masks/manage/actor_controller.rb +35 -0
- data/app/controllers/masks/manage/actors_controller.rb +12 -0
- data/app/controllers/masks/manage/base_controller.rb +12 -0
- data/app/controllers/masks/one_time_code_controller.rb +49 -0
- data/app/controllers/masks/passwords_controller.rb +33 -0
- data/app/controllers/masks/recoveries_controller.rb +43 -0
- data/app/controllers/masks/sessions_controller.rb +53 -0
- data/app/helpers/masks/application_helper.rb +49 -0
- data/app/jobs/masks/application_job.rb +7 -0
- data/app/jobs/masks/expire_actors_job.rb +15 -0
- data/app/jobs/masks/expire_recoveries_job.rb +15 -0
- data/app/mailers/masks/actor_mailer.rb +22 -0
- data/app/mailers/masks/application_mailer.rb +15 -0
- data/app/models/concerns/masks/access.rb +162 -0
- data/app/models/concerns/masks/actor.rb +132 -0
- data/app/models/concerns/masks/adapter.rb +68 -0
- data/app/models/concerns/masks/role.rb +9 -0
- data/app/models/concerns/masks/scoped.rb +54 -0
- data/app/models/masks/access/actor_password.rb +20 -0
- data/app/models/masks/access/actor_scopes.rb +18 -0
- data/app/models/masks/access/actor_signup.rb +22 -0
- data/app/models/masks/actors/anonymous.rb +40 -0
- data/app/models/masks/actors/system.rb +24 -0
- data/app/models/masks/adapters/active_record.rb +85 -0
- data/app/models/masks/application_model.rb +15 -0
- data/app/models/masks/application_record.rb +8 -0
- data/app/models/masks/check.rb +192 -0
- data/app/models/masks/credential.rb +166 -0
- data/app/models/masks/credentials/backup_code.rb +30 -0
- data/app/models/masks/credentials/device.rb +59 -0
- data/app/models/masks/credentials/email.rb +48 -0
- data/app/models/masks/credentials/factor2.rb +71 -0
- data/app/models/masks/credentials/key.rb +38 -0
- data/app/models/masks/credentials/last_login.rb +12 -0
- data/app/models/masks/credentials/masquerade.rb +32 -0
- data/app/models/masks/credentials/nickname.rb +63 -0
- data/app/models/masks/credentials/one_time_code.rb +34 -0
- data/app/models/masks/credentials/password.rb +28 -0
- data/app/models/masks/credentials/recovery.rb +71 -0
- data/app/models/masks/credentials/session.rb +67 -0
- data/app/models/masks/device.rb +30 -0
- data/app/models/masks/error.rb +51 -0
- data/app/models/masks/event.rb +14 -0
- data/app/models/masks/mask.rb +255 -0
- data/app/models/masks/rails/actor.rb +190 -0
- data/app/models/masks/rails/actor_role.rb +12 -0
- data/app/models/masks/rails/device.rb +47 -0
- data/app/models/masks/rails/email.rb +96 -0
- data/app/models/masks/rails/key.rb +61 -0
- data/app/models/masks/rails/recovery.rb +116 -0
- data/app/models/masks/rails/role.rb +20 -0
- data/app/models/masks/rails/scope.rb +15 -0
- data/app/models/masks/session.rb +447 -0
- data/app/models/masks/sessions/access.rb +26 -0
- data/app/models/masks/sessions/inline.rb +16 -0
- data/app/models/masks/sessions/request.rb +42 -0
- data/app/resources/masks/actor_resource.rb +9 -0
- data/app/resources/masks/session_resource.rb +15 -0
- data/app/views/layouts/masks/application.html.erb +17 -0
- data/app/views/layouts/masks/mailer.html.erb +17 -0
- data/app/views/layouts/masks/mailer.text.erb +1 -0
- data/app/views/layouts/masks/manage.html.erb +25 -0
- data/app/views/masks/actor_mailer/recover_credentials.html.erb +33 -0
- data/app/views/masks/actor_mailer/recover_credentials.text.erb +1 -0
- data/app/views/masks/actor_mailer/verify_email.html.erb +34 -0
- data/app/views/masks/actor_mailer/verify_email.text.erb +8 -0
- data/app/views/masks/actors/current.html.erb +152 -0
- data/app/views/masks/application/_header.html.erb +31 -0
- data/app/views/masks/backup_codes/new.html.erb +103 -0
- data/app/views/masks/emails/new.html.erb +103 -0
- data/app/views/masks/emails/verify.html.erb +51 -0
- data/app/views/masks/keys/new.html.erb +127 -0
- data/app/views/masks/manage/actor/show.html.erb +126 -0
- data/app/views/masks/manage/actors/index.html.erb +40 -0
- data/app/views/masks/one_time_code/new.html.erb +150 -0
- data/app/views/masks/passwords/edit.html.erb +58 -0
- data/app/views/masks/recoveries/new.html.erb +71 -0
- data/app/views/masks/recoveries/password.html.erb +64 -0
- data/app/views/masks/sessions/new.html.erb +153 -0
- data/config/brakeman.ignore +28 -0
- data/config/locales/en.yml +286 -0
- data/config/routes.rb +46 -0
- data/db/migrate/20231205173845_create_actors.rb +94 -0
- data/lib/generators/masks/install/USAGE +8 -0
- data/lib/generators/masks/install/install_generator.rb +33 -0
- data/lib/generators/masks/install/templates/initializer.rb +5 -0
- data/lib/generators/masks/install/templates/masks.json +6 -0
- data/lib/masks/configuration.rb +236 -0
- data/lib/masks/engine.rb +25 -0
- data/lib/masks/middleware.rb +70 -0
- data/lib/masks/version.rb +5 -0
- data/lib/masks.rb +183 -0
- data/lib/tasks/masks_tasks.rake +71 -0
- data/masks.json +274 -0
- metadata +416 -0
|
Binary file
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { Controller } from "@hotwired/stimulus";
|
|
2
|
+
|
|
3
|
+
export default class extends Controller {
|
|
4
|
+
static get targets() {
|
|
5
|
+
return ["email", "password", "submit"];
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
connect() {
|
|
9
|
+
this.email = this.emailTarget.value;
|
|
10
|
+
this.pass = this.passwordTarget.value;
|
|
11
|
+
|
|
12
|
+
this.syncState();
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
updateEmail(e) {
|
|
16
|
+
this.email = e.target.value;
|
|
17
|
+
this.syncState();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
updatePassword(e) {
|
|
21
|
+
this.pass = e.target.value;
|
|
22
|
+
this.syncState();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
syncState() {
|
|
26
|
+
this.submitTarget.disabled = !this.email?.includes("@") || !this.pass;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { application } from "./application";
|
|
2
|
+
|
|
3
|
+
import SessionController from "./session_controller";
|
|
4
|
+
import RecoverController from "./recover_controller";
|
|
5
|
+
import RecoverPasswordController from "./recover_password_controller";
|
|
6
|
+
import EmailsController from "./emails_controller";
|
|
7
|
+
import KeysController from "./keys_controller";
|
|
8
|
+
application.register("session", SessionController);
|
|
9
|
+
application.register("recover", RecoverController);
|
|
10
|
+
application.register("recover-password", RecoverPasswordController);
|
|
11
|
+
application.register("emails", EmailsController);
|
|
12
|
+
application.register("keys", KeysController);
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Controller } from "@hotwired/stimulus";
|
|
2
|
+
|
|
3
|
+
export default class extends Controller {
|
|
4
|
+
static get targets() {
|
|
5
|
+
return ["settings"];
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
connect() {
|
|
9
|
+
// this.email = this.;emailTarget.value;
|
|
10
|
+
// this.pass = this.passwordTarget.value;
|
|
11
|
+
// this.syncState();
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
toggleSettings(e) {
|
|
15
|
+
this.settingsTarget.classList.toggle("hidden");
|
|
16
|
+
|
|
17
|
+
e.preventDefault();
|
|
18
|
+
e.stopPropagation();
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Controller } from "@hotwired/stimulus";
|
|
2
|
+
|
|
3
|
+
export default class extends Controller {
|
|
4
|
+
static get targets() {
|
|
5
|
+
return ["input", "submit"];
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
connect() {
|
|
9
|
+
this.input = this.inputTarget.value;
|
|
10
|
+
this.syncState();
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
updateInput(e) {
|
|
14
|
+
this.input = e.target.value;
|
|
15
|
+
this.syncState();
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
syncState() {
|
|
19
|
+
this.submitTarget.disabled = !this.input?.length;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Controller } from "@hotwired/stimulus";
|
|
2
|
+
|
|
3
|
+
export default class extends Controller {
|
|
4
|
+
static get targets() {
|
|
5
|
+
return ["password", "submit"];
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
connect() {
|
|
9
|
+
this.password = this.passwordTarget.value;
|
|
10
|
+
this.syncState();
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
updatePassword(e) {
|
|
14
|
+
this.password = e.target.value;
|
|
15
|
+
this.syncState();
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
syncState() {
|
|
19
|
+
this.submitTarget.disabled = !this.password?.length;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { Controller } from "@hotwired/stimulus";
|
|
2
|
+
|
|
3
|
+
export default class extends Controller {
|
|
4
|
+
static get targets() {
|
|
5
|
+
return [
|
|
6
|
+
"nickname",
|
|
7
|
+
"oneTimeCode",
|
|
8
|
+
"backupCode",
|
|
9
|
+
"password",
|
|
10
|
+
"submit",
|
|
11
|
+
"remember",
|
|
12
|
+
"flash",
|
|
13
|
+
];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
connect() {
|
|
17
|
+
this.session = this.hasNicknameTarget
|
|
18
|
+
? {
|
|
19
|
+
nickname: this.nicknameTarget.value,
|
|
20
|
+
password: this.passwordTarget.value,
|
|
21
|
+
}
|
|
22
|
+
: {};
|
|
23
|
+
|
|
24
|
+
if (this.hasOneTimeCodeTarget) {
|
|
25
|
+
this.session.code = this.oneTimeCodeTarget.value;
|
|
26
|
+
this.session.factor2 = true;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (this.hasBackupCodeTarget) {
|
|
30
|
+
this.session.code = this.backupCodeTarget.value;
|
|
31
|
+
this.session.factor2 = true;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
this.syncState();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
updateCode(e) {
|
|
38
|
+
this.session.code = e.target.value;
|
|
39
|
+
this.syncState();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
updateNickname(e) {
|
|
43
|
+
this.session.nickname = e.target.value;
|
|
44
|
+
this.syncState();
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
updatePassword(e) {
|
|
48
|
+
this.session.password = e.target.value;
|
|
49
|
+
this.syncState();
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
syncState() {
|
|
53
|
+
this.submitTarget.classList.add("btn-accent");
|
|
54
|
+
|
|
55
|
+
let flash;
|
|
56
|
+
|
|
57
|
+
if (this.session.nickname && this.session.password) {
|
|
58
|
+
this.submitTarget.disabled = false;
|
|
59
|
+
this.rememberTarget.classList.remove("hidden");
|
|
60
|
+
|
|
61
|
+
flash = "continue";
|
|
62
|
+
} else if (this.session.factor2) {
|
|
63
|
+
const disabled = (this.session.code?.length || 0) < 6;
|
|
64
|
+
this.submitTarget.disabled = disabled;
|
|
65
|
+
|
|
66
|
+
if (disabled) {
|
|
67
|
+
this.rememberTarget.classList.add("hidden");
|
|
68
|
+
} else {
|
|
69
|
+
this.rememberTarget.classList.remove("hidden");
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
flash = "enter-factor2";
|
|
73
|
+
} else {
|
|
74
|
+
this.submitTarget.disabled = true;
|
|
75
|
+
this.rememberTarget.classList.add("hidden");
|
|
76
|
+
|
|
77
|
+
if (this.session.nickname) {
|
|
78
|
+
flash = "enter-password";
|
|
79
|
+
} else {
|
|
80
|
+
flash = "enter-credentials";
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (flash) {
|
|
85
|
+
for (let el of this.flashTarget.querySelectorAll("[data-flash]")) {
|
|
86
|
+
if (el.dataset.flash == flash) {
|
|
87
|
+
el.classList.remove("hidden");
|
|
88
|
+
} else {
|
|
89
|
+
el.classList.add("hidden");
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* This is a manifest file that'll be compiled into application.css, which will include all the files
|
|
3
|
+
* listed below.
|
|
4
|
+
*
|
|
5
|
+
* Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
|
|
6
|
+
* or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
|
|
7
|
+
*
|
|
8
|
+
* You're free to add application-wide styles to this file and they'll appear at the bottom of the
|
|
9
|
+
* compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS
|
|
10
|
+
* files in this directory. Styles in this file should be added after the last require_* statement.
|
|
11
|
+
* It is generally better to create a new file per style scope.
|
|
12
|
+
*
|
|
13
|
+
*= require_tree .
|
|
14
|
+
*= require_self
|
|
15
|
+
*/
|
|
16
|
+
@tailwind base;
|
|
17
|
+
@tailwind components;
|
|
18
|
+
@tailwind utilities;
|
|
19
|
+
|
|
20
|
+
.pagination {
|
|
21
|
+
@apply flex gap-2 items-center justify-center p-4;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.pagination .page {
|
|
25
|
+
@apply btn btn-sm;
|
|
26
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Masks
|
|
4
|
+
# Helpers for interacting with Masks in Rails controllers.
|
|
5
|
+
#
|
|
6
|
+
# @see ClassMethods
|
|
7
|
+
module Controller
|
|
8
|
+
extend ActiveSupport::Concern
|
|
9
|
+
|
|
10
|
+
module ClassMethods
|
|
11
|
+
# Require a session masked by the type passed.
|
|
12
|
+
#
|
|
13
|
+
# If this fails +unathorized_access+ is called.
|
|
14
|
+
#
|
|
15
|
+
# @param [Symbol|String] type
|
|
16
|
+
# @param [Hash] opts forwarded to +before_action+
|
|
17
|
+
def require_mask(type: nil, **opts)
|
|
18
|
+
before_action(
|
|
19
|
+
:unauthorized_access,
|
|
20
|
+
unless: -> { masked?(type:) },
|
|
21
|
+
**opts
|
|
22
|
+
)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Builds an access object by the passed name, if allowed.
|
|
26
|
+
#
|
|
27
|
+
# If this succeeds, the controller instance's +current_access+ will return
|
|
28
|
+
# the constructed access class. Otherwise, on failure the
|
|
29
|
+
# +unathorized_access+ method is called.
|
|
30
|
+
#
|
|
31
|
+
# @example
|
|
32
|
+
# class MyController < ApplicationController
|
|
33
|
+
# include Masks::Controller
|
|
34
|
+
#
|
|
35
|
+
# require_access 'my.example', only: :index
|
|
36
|
+
#
|
|
37
|
+
# # this action is only called if the session is
|
|
38
|
+
# # able to build the "my.example" access class.
|
|
39
|
+
# def index
|
|
40
|
+
# render json: {
|
|
41
|
+
# foobar: current_access.foo_bar
|
|
42
|
+
# }
|
|
43
|
+
# end
|
|
44
|
+
# end
|
|
45
|
+
#
|
|
46
|
+
# @param [Symbol|String] name
|
|
47
|
+
# @param [Hash] opts forwarded to +before_action+
|
|
48
|
+
def require_access(name, **opts)
|
|
49
|
+
before_action(
|
|
50
|
+
:unauthorized_access,
|
|
51
|
+
unless: -> { build_access(name) },
|
|
52
|
+
**opts
|
|
53
|
+
)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
protected
|
|
58
|
+
|
|
59
|
+
def passed?
|
|
60
|
+
current_actor && masked_session.passed?
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def unauthorized_access
|
|
64
|
+
render plain: "", status: :unauthorized
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Returns the current masks session for the request.
|
|
68
|
+
#
|
|
69
|
+
# @return [Masks::Session]
|
|
70
|
+
# @visibility public
|
|
71
|
+
def masked_session
|
|
72
|
+
@masked_session ||= request.env[Masks::Middleware::SESSION_KEY]
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Returns the mask for the request.
|
|
76
|
+
#
|
|
77
|
+
# @return [Masks::Mask]
|
|
78
|
+
# @visibility public
|
|
79
|
+
def current_mask
|
|
80
|
+
masked_session.mask
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Returns the mask for the request.
|
|
84
|
+
#
|
|
85
|
+
# @return [Masks::Actor]
|
|
86
|
+
# @visibility public
|
|
87
|
+
def current_actor
|
|
88
|
+
masked_session.scoped
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Returns the mask for the request.
|
|
92
|
+
#
|
|
93
|
+
# @return [Masks::Access]
|
|
94
|
+
# @visibility public
|
|
95
|
+
def current_access
|
|
96
|
+
@current_access
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def build_access(name)
|
|
100
|
+
@current_access = Masks.access(name, masked_session)
|
|
101
|
+
rescue Masks::Error::Unauthorized
|
|
102
|
+
false
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def masked?(type: nil)
|
|
106
|
+
if type &&
|
|
107
|
+
!Array.wrap(type).map(&:to_s).include?(masked_session.mask.type)
|
|
108
|
+
false
|
|
109
|
+
else
|
|
110
|
+
masked_session.passed?
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Masks
|
|
4
|
+
# @visibility private
|
|
5
|
+
class ActorsController < ApplicationController
|
|
6
|
+
require_mask type: %i[session api], only: :current
|
|
7
|
+
|
|
8
|
+
def current
|
|
9
|
+
respond_to do |format|
|
|
10
|
+
format.json { render json: ActorResource.new(current_actor) }
|
|
11
|
+
format.html { render(:current) }
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Masks
|
|
4
|
+
# @visibility private
|
|
5
|
+
class ApplicationController < ActionController::Base
|
|
6
|
+
include Masks::Controller
|
|
7
|
+
include Pagy::Backend
|
|
8
|
+
|
|
9
|
+
before_action :assign_session
|
|
10
|
+
|
|
11
|
+
skip_before_action :verify_authenticity_token, if: :json_request?
|
|
12
|
+
|
|
13
|
+
protect_from_forgery with: :exception
|
|
14
|
+
|
|
15
|
+
private
|
|
16
|
+
|
|
17
|
+
def json_request?
|
|
18
|
+
request.format.symbol == :json
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def assign_session
|
|
22
|
+
@session = masked_session
|
|
23
|
+
@config = @session.config
|
|
24
|
+
@actor = @session.actor
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def require_sudo(redirect)
|
|
28
|
+
return if current_mask.type == "sudo" && passed?
|
|
29
|
+
|
|
30
|
+
flash[:errors] = ["enter a valid password"]
|
|
31
|
+
|
|
32
|
+
redirect_to redirect
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Masks
|
|
4
|
+
# @visibility private
|
|
5
|
+
class BackupCodesController < ApplicationController
|
|
6
|
+
require_mask type: :session, only: :new
|
|
7
|
+
before_action only: %i[create] do
|
|
8
|
+
require_sudo(backup_codes_path)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def new
|
|
12
|
+
respond_to { |format| format.html { render(:new) } }
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def create
|
|
16
|
+
if create_params[:reset]
|
|
17
|
+
@actor.saved_backup_codes_at = nil
|
|
18
|
+
@actor.backup_codes = nil
|
|
19
|
+
@actor.save
|
|
20
|
+
elsif create_params[:enable]
|
|
21
|
+
@actor.saved_backup_codes_at = Time.current
|
|
22
|
+
@actor.save
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
respond_to { |format| format.html { redirect_to backup_codes_path } }
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
def create_params
|
|
31
|
+
params.require(:backup_codes).permit(:reset, :enable)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Masks
|
|
4
|
+
# @visibility private
|
|
5
|
+
class DevicesController < ApplicationController
|
|
6
|
+
require_mask type: :session, only: :update
|
|
7
|
+
|
|
8
|
+
def update
|
|
9
|
+
device =
|
|
10
|
+
masked_session.config.find_device(masked_session, key: params[:key])
|
|
11
|
+
|
|
12
|
+
if device&.persisted? && params[:reset]
|
|
13
|
+
device.reset_version
|
|
14
|
+
device.save!
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
redirect_back_or_to "/"
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Masks
|
|
4
|
+
# @visibility private
|
|
5
|
+
class EmailsController < ApplicationController
|
|
6
|
+
require_mask type: :session, only: :new
|
|
7
|
+
before_action :require_sudo, only: :create
|
|
8
|
+
|
|
9
|
+
def new
|
|
10
|
+
@emails = current_actor.emails
|
|
11
|
+
|
|
12
|
+
respond_to { |format| format.html { render(:new) } }
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def create
|
|
16
|
+
@email = current_actor.emails.build(email: email_param.strip)
|
|
17
|
+
|
|
18
|
+
if @email.valid?
|
|
19
|
+
@email.notify!(masked_session)
|
|
20
|
+
else
|
|
21
|
+
flash[:errors] = @email.errors.full_messages
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
respond_to { |format| format.html { redirect_to emails_path } }
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def notify
|
|
28
|
+
@email = current_actor.emails.find_by(email: email_param.strip)
|
|
29
|
+
@email.notify!(masked_session) if @email.expired?
|
|
30
|
+
|
|
31
|
+
respond_to { |format| format.html { redirect_to emails_path } }
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def delete
|
|
35
|
+
@email = current_actor.emails.find_by(email: email_param)
|
|
36
|
+
@email&.destroy
|
|
37
|
+
|
|
38
|
+
respond_to { |format| format.html { redirect_to emails_path } }
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def verify
|
|
42
|
+
@email = current_actor.emails.find_by(token: params[:email])
|
|
43
|
+
@email&.verify! if @email&.valid?
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
private
|
|
47
|
+
|
|
48
|
+
def email_param
|
|
49
|
+
params.dig(:email, :value)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def require_sudo
|
|
53
|
+
return if current_mask.type == "sudo" && passed?
|
|
54
|
+
|
|
55
|
+
flash[:errors] = ["enter a valid password"]
|
|
56
|
+
|
|
57
|
+
redirect_to emails_path
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Masks
|
|
4
|
+
# @visibility private
|
|
5
|
+
class ErrorController < ApplicationController
|
|
6
|
+
def render_unauthorized
|
|
7
|
+
render plain: "", status: 401
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def redirect
|
|
11
|
+
redirect_to masked_session.mask.fail
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Masks
|
|
4
|
+
# @visibility private
|
|
5
|
+
class KeysController < ApplicationController
|
|
6
|
+
require_mask type: :session
|
|
7
|
+
|
|
8
|
+
def new
|
|
9
|
+
@keys = current_actor.keys
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def create
|
|
13
|
+
key =
|
|
14
|
+
current_actor.keys.build(
|
|
15
|
+
name: create_params[:name],
|
|
16
|
+
secret: create_params[:secret]&.presence,
|
|
17
|
+
scopes: create_params[:scopes]
|
|
18
|
+
)
|
|
19
|
+
key.save
|
|
20
|
+
|
|
21
|
+
if key.valid?
|
|
22
|
+
flash[:key] = { name: key.name, secret: key.secret }
|
|
23
|
+
else
|
|
24
|
+
flash[:error] = key.errors.full_messages.first
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
redirect_back_or_to keys_path
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def delete
|
|
31
|
+
key = current_actor.keys.find(params[:id])
|
|
32
|
+
key.destroy
|
|
33
|
+
|
|
34
|
+
flash[:notice] = "#{key.name} destroyed" if key.destroyed?
|
|
35
|
+
|
|
36
|
+
redirect_back_or_to "/"
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
private
|
|
40
|
+
|
|
41
|
+
def create_params
|
|
42
|
+
params.require(:key).permit(:name, :secret, scopes: [])
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Masks
|
|
4
|
+
# @visibility private
|
|
5
|
+
module Manage
|
|
6
|
+
class ActorController < BaseController
|
|
7
|
+
before_action :find_actor
|
|
8
|
+
|
|
9
|
+
def update
|
|
10
|
+
Masks::Rails::Actor.transaction do
|
|
11
|
+
if params[:add_scope]
|
|
12
|
+
@actor.assign_scopes!(params[:add_scope])
|
|
13
|
+
flash[:info] = "added scope"
|
|
14
|
+
elsif params[:remove_scope]
|
|
15
|
+
@actor.remove_scopes!(params[:remove_scope])
|
|
16
|
+
flash[:info] = "removed scope"
|
|
17
|
+
elsif params[:remove_factor2]
|
|
18
|
+
@actor.remove_factor2! if params[:remove_factor2]
|
|
19
|
+
flash[:info] = "removed second factor authentication"
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
redirect_to actor_path(@actor)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
@actor
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
def find_actor
|
|
31
|
+
@actor = Masks::Rails::Actor.find(params[:actor])
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|