self-auth-rails 0.1.1
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 +68 -0
- data/Rakefile +8 -0
- data/app/assets/config/self-auth-rails/app.js +89 -0
- data/app/assets/config/self-auth-rails/cable.js +14 -0
- data/app/assets/config/self-auth-rails/channels/auth.js +7 -0
- data/app/assets/config/self-auth-rails/channels/messages_channel.js +150 -0
- data/app/assets/config/self-auth-rails/cookies.js +2 -0
- data/app/assets/config/self-auth-rails/ki.js +8 -0
- data/app/assets/config/self-auth-rails/manifest.js +8 -0
- data/app/assets/stylesheets/self-auth-rails/application.css +27 -0
- data/app/channels/application_cable/channel.rb +4 -0
- data/app/channels/application_cable/connection.rb +15 -0
- data/app/channels/messages_channel.rb +9 -0
- data/app/controllers/self_auth_rails/application_controller.rb +4 -0
- data/app/controllers/self_auth_rails/sessions_controller.rb +90 -0
- data/app/helpers/self_auth_rails/application_helper.rb +24 -0
- data/app/helpers/self_auth_rails/sessions_helper.rb +4 -0
- data/app/jobs/self_auth_rails/application_job.rb +4 -0
- data/app/mailers/self_auth_rails/application_mailer.rb +6 -0
- data/app/models/self_auth_rails/application_record.rb +5 -0
- data/app/services/self_auth_response_manager_service.rb +76 -0
- data/app/views/self_auth_rails/sessions/create.html.erb +2 -0
- data/app/views/self_auth_rails/sessions/new.html.erb +75 -0
- data/config/routes.rb +11 -0
- data/lib/self-auth-rails/engine.rb +18 -0
- data/lib/self-auth-rails/version.rb +3 -0
- data/lib/self-auth-rails.rb +50 -0
- data/lib/tasks/initializer.rb.tpl +45 -0
- data/lib/tasks/self_auth_rails_tasks.rake +69 -0
- metadata +116 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: f7bf53acb259dc5c315fcf6222d30d099a3c95ada6b9bacb5d51554d8dc6ec08
|
4
|
+
data.tar.gz: 7d0fb909e65049f4f3ae8a16ccb15d0f82510a8d1f03bab09b84b4187aad37b2
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 51c1afac76389b629e41e293dd2e1d21826e3629602b3f7a770073a1731168cec1ccec7a66cdcf1a5c61c8d87b8772b9ee28302b6b3296cd92ad2fbe2c1ed477
|
7
|
+
data.tar.gz: 10e87468d332a8106727f5ecb2c75eab382163df0fbe5468d91dbc11e3a69fcb2fd4e7befdd01a089c8dae1024ef3183b79ceb1a1e295f457ee075e8dde0e61c
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2022 Self Group Ltd.
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
# self-auth-rails
|
2
|
+
|
3
|
+
self-auth-rails is rails engine to easily integrate [Self Authentication](https://docs.joinself.com/authentication/) on your authentication workflow.
|
4
|
+
|
5
|
+
## Usage
|
6
|
+
How to use my plugin.
|
7
|
+
|
8
|
+
## Getting started
|
9
|
+
### Installation
|
10
|
+
Add this line to your application's Gemfile:
|
11
|
+
|
12
|
+
```ruby
|
13
|
+
gem "self-auth-rails"
|
14
|
+
```
|
15
|
+
|
16
|
+
And then execute:
|
17
|
+
```bash
|
18
|
+
$ bundle install
|
19
|
+
```
|
20
|
+
|
21
|
+
### Set up
|
22
|
+
|
23
|
+
For your convenience `self-auth-rails` comes with a script to automatically set up the initializers and mounting routes on your system.
|
24
|
+
|
25
|
+
```
|
26
|
+
$ rake self_auth_rails:init
|
27
|
+
```
|
28
|
+
|
29
|
+
This command will
|
30
|
+
- [x] Generate necessary migrations
|
31
|
+
- [x] Create initializers (config/initializers/self_auth_rails.rb)
|
32
|
+
- [x] Mount the engine on your routes (config/routes.rb)
|
33
|
+
|
34
|
+
Once that's done you'll need to:
|
35
|
+
|
36
|
+
- [ ] run the created migrations with `bin/rails db:migrate`
|
37
|
+
- [ ] setup the environment variables required on `config/initializers/self_auth_rails.rb`
|
38
|
+
- [ ] protect the resources you want to hide from the public
|
39
|
+
|
40
|
+
### Protecting routes
|
41
|
+
|
42
|
+
You can fully protect your app by adding a controller filter to your `app/controllers/application.rb`, or specifying it only for the routes you want to be protected.
|
43
|
+
```
|
44
|
+
class ApplicationController < ActionController::Base
|
45
|
+
before_action :authenticate_user!
|
46
|
+
end
|
47
|
+
```
|
48
|
+
|
49
|
+
### Running it
|
50
|
+
|
51
|
+
That's it, as soon as you have the SELF required environment variables in place, you'll be able to see the authentication screen when you visit http://localhost:3000.
|
52
|
+
|
53
|
+
|
54
|
+
### Notes
|
55
|
+
#### Action cable
|
56
|
+
By default action cable relies on redis to work on development, if you're just testing this gem, you can switch it to `async` on `config/cable.yml`
|
57
|
+
```
|
58
|
+
development:
|
59
|
+
adapter: async
|
60
|
+
|
61
|
+
#...
|
62
|
+
```
|
63
|
+
|
64
|
+
## Contributing
|
65
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/joinself/self-auth-rails.
|
66
|
+
|
67
|
+
## License
|
68
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
/* Initialize app when page loads */
|
2
|
+
$(function(){
|
3
|
+
var Messages = App.cable.subscriptions.create({
|
4
|
+
channel:'MessagesChannel',
|
5
|
+
conversation_id: connectionID()
|
6
|
+
}, {
|
7
|
+
received: function(data) {
|
8
|
+
console.log(data);
|
9
|
+
if(data.type == "info") {
|
10
|
+
if(data.status == "completed") {
|
11
|
+
console.log("info request");
|
12
|
+
window.location.replace("/");
|
13
|
+
} else {
|
14
|
+
message = "Fact request has been rejected"
|
15
|
+
if(data.status == "errored") {
|
16
|
+
message = data.message
|
17
|
+
}
|
18
|
+
// TODO: this id no longer exists...
|
19
|
+
$('#info_messages').append("<div class='alert alert-danger'><button type='button' class='close' data-dismiss='alert' aria-hidden='true'>×</button>'+message+'</div>");
|
20
|
+
}
|
21
|
+
} else {
|
22
|
+
$("#messages").removeClass('hidden');
|
23
|
+
if(data.status == "accepted") {
|
24
|
+
// Just display the
|
25
|
+
$('#process').removeClass("hidden");
|
26
|
+
$('#non-dl-container').addClass("hidden");
|
27
|
+
$('#dl-container').addClass("hidden");
|
28
|
+
|
29
|
+
// Login request accepted
|
30
|
+
$("#req-auth .activity-icon").addClass("bg-success")
|
31
|
+
$("#req-auth .activity-icon").addClass("shadow-success")
|
32
|
+
$("#req-auth .activity-icon").removeClass("bg-info")
|
33
|
+
$("#req-auth .activity-icon").removeClass("shadow-info")
|
34
|
+
|
35
|
+
$("#req-info .activity-icon").removeClass("bg-secondary")
|
36
|
+
$("#req-info .activity-icon").removeClass("shadow-secondary")
|
37
|
+
$("#req-info .activity-icon").addClass("bg-info")
|
38
|
+
$("#req-info .activity-icon").addClass("shadow-info")
|
39
|
+
|
40
|
+
$("#req-auth .waiting").addClass("hidden")
|
41
|
+
$("#req-auth .passed").removeClass("hidden")
|
42
|
+
$("#req-info .waiting").removeClass("hidden")
|
43
|
+
|
44
|
+
} else if(data.status == "completed") {
|
45
|
+
$("#auth-ok").hide()
|
46
|
+
|
47
|
+
$("#req-info .activity-icon").addClass("bg-success")
|
48
|
+
$("#req-info .activity-icon").addClass("shadow-success")
|
49
|
+
$("#req-info .activity-icon").removeClass("bg-info")
|
50
|
+
$("#req-info .activity-icon").removeClass("shadow-info")
|
51
|
+
|
52
|
+
$("#req-info .waiting").addClass("hidden")
|
53
|
+
$("#req-info .passed").removeClass("hidden")
|
54
|
+
$(".logging-in").removeClass("hidden")
|
55
|
+
$(".btn-cancel").addClass("hidden")
|
56
|
+
|
57
|
+
setTimeout(() => {
|
58
|
+
// Fetched user information
|
59
|
+
$("#login-token")[0].value = data.token;
|
60
|
+
$("#form-login").off("submit")
|
61
|
+
$("#form-login")[0].submit();
|
62
|
+
}, 2000);
|
63
|
+
|
64
|
+
} else {
|
65
|
+
|
66
|
+
message = "Login request has been rejected"
|
67
|
+
if(data.status == "errored") {
|
68
|
+
message = data.message
|
69
|
+
}
|
70
|
+
$('#messages').append(`<div class="alert alert-warning alert-dismissible show fade"><div class="alert-body"><button class="close" data-dismiss="alert"><span>×</span></button>${message}</div></div>`);
|
71
|
+
|
72
|
+
$('#messages').show();
|
73
|
+
$('#process').addClass("hidden");
|
74
|
+
$('#non-dl-container').removeClass("hidden");
|
75
|
+
|
76
|
+
$("#req-auth .activity-icon").addClass("bg-info")
|
77
|
+
$("#req-auth .activity-icon").addClass("shadow-info")
|
78
|
+
$("#req-auth .activity-icon").removeClass("bg-success")
|
79
|
+
$("#req-auth .activity-icon").removeClass("shadow-success")
|
80
|
+
|
81
|
+
$("#req-info .activity-icon").removeClass("bg-info")
|
82
|
+
$("#req-info .activity-icon").removeClass("shadow-info")
|
83
|
+
$("#req-info .activity-icon").addClass("bg-secondary")
|
84
|
+
$("#req-info .activity-icon").addClass("shadow-secondary")
|
85
|
+
}
|
86
|
+
}
|
87
|
+
}
|
88
|
+
});
|
89
|
+
});
|
@@ -0,0 +1,14 @@
|
|
1
|
+
// Action Cable provides the framework to deal with WebSockets in Rails.
|
2
|
+
// You can generate new channels where WebSocket features live using the `rails generate channel` command.
|
3
|
+
//
|
4
|
+
//= require actioncable
|
5
|
+
//= require_self
|
6
|
+
//= require_tree ./channels
|
7
|
+
|
8
|
+
(function() {
|
9
|
+
this.App || (this.App = {});
|
10
|
+
|
11
|
+
App.cable = ActionCable.createConsumer();
|
12
|
+
|
13
|
+
}).call(this);
|
14
|
+
|
@@ -0,0 +1,150 @@
|
|
1
|
+
function uuidv4() {
|
2
|
+
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
|
3
|
+
var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
|
4
|
+
return v.toString(16);
|
5
|
+
});
|
6
|
+
}
|
7
|
+
|
8
|
+
function connectionID() {
|
9
|
+
c = Cookies.get("auth-sess")
|
10
|
+
if(c == undefined) {
|
11
|
+
Cookies.set('auth-sess', uuidv4());
|
12
|
+
}
|
13
|
+
return Cookies.get("auth-sess");
|
14
|
+
}
|
15
|
+
|
16
|
+
function displayQR() {
|
17
|
+
updateQR();
|
18
|
+
$('#qr-container').show();
|
19
|
+
$('#page-wrapper').addClass('page-loading');
|
20
|
+
window.qrInterval = window.setInterval(function(){
|
21
|
+
if($('#qr').length) {
|
22
|
+
updateQR();
|
23
|
+
}
|
24
|
+
}, 30000);
|
25
|
+
}
|
26
|
+
|
27
|
+
function hideQR() {
|
28
|
+
location.reload();
|
29
|
+
}
|
30
|
+
|
31
|
+
function updateQR() {
|
32
|
+
if($("#qr_url").length) {
|
33
|
+
$("#qr")[0].setAttribute("src", $("#qr_url")[0].value + "?uuid=" + connectionID());
|
34
|
+
}
|
35
|
+
}
|
36
|
+
|
37
|
+
function updateDL() {
|
38
|
+
if($("#dl_url").length) {
|
39
|
+
if (/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|ipad|iris|kindle|Android|Silk|lge |maemo|midp|mmp|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino/i.test(navigator.userAgent)
|
40
|
+
|| /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(navigator.userAgent.substr(0, 4))) {
|
41
|
+
$("#dl-container").removeClass("hidden");
|
42
|
+
$("#non-dl-container").addClass("hidden");
|
43
|
+
|
44
|
+
$.getJSON($("#dl_url")[0].value + "?uuid=" + connectionID(), function (data) {
|
45
|
+
$("#dl").attr("href", data["url"]);
|
46
|
+
|
47
|
+
$('#dl').unbind('click');
|
48
|
+
$('#dl').bind("click", function (e) {
|
49
|
+
$('#page-wrapper').addClass('page-loading');
|
50
|
+
if($("#profile_request").length) {
|
51
|
+
hasProfile();
|
52
|
+
}
|
53
|
+
});
|
54
|
+
});
|
55
|
+
}
|
56
|
+
}
|
57
|
+
}
|
58
|
+
|
59
|
+
function hasProfile() {
|
60
|
+
$.ajax({
|
61
|
+
type: "GET",
|
62
|
+
contentType: "application/json",
|
63
|
+
url: "/session/has_profile",
|
64
|
+
dataType: 'json',
|
65
|
+
timeout: 600000,
|
66
|
+
success: function (data) {
|
67
|
+
window.location.replace("/");
|
68
|
+
},
|
69
|
+
error: function (e) {
|
70
|
+
setTimeout(function(){ hasProfile(); }, 2000);
|
71
|
+
}
|
72
|
+
});
|
73
|
+
}
|
74
|
+
|
75
|
+
ready = function() {
|
76
|
+
|
77
|
+
if($("#qr-container")){
|
78
|
+
displayQR();
|
79
|
+
}
|
80
|
+
if($("#dl-container")) {
|
81
|
+
updateDL();
|
82
|
+
}
|
83
|
+
|
84
|
+
$("#switch-not-dl").bind("click", function(e){
|
85
|
+
$("#dl-container").addClass("hidden");
|
86
|
+
$("#non-dl-container").removeClass("hidden");
|
87
|
+
$("#selfid-field").focus();
|
88
|
+
$("#qr").width($(".qr-wrapper").width());
|
89
|
+
$("#qr").height($(".qr-wrapper").width());
|
90
|
+
});
|
91
|
+
|
92
|
+
$("#profile_request").bind("click", function (e) {
|
93
|
+
e.preventDefault(e);
|
94
|
+
$("#profile_help_block").show();
|
95
|
+
console.log("sending ajax request")
|
96
|
+
$.ajax({
|
97
|
+
type: "POST",
|
98
|
+
contentType: "application/json",
|
99
|
+
url: "/profile/fetch",
|
100
|
+
dataType: 'json',
|
101
|
+
data: '{"user":{"connection_id":"'+connectionID()+'"}}',
|
102
|
+
beforeSend: $.rails.CSRFProtection,
|
103
|
+
timeout: 600000,
|
104
|
+
success: function (data) {
|
105
|
+
},
|
106
|
+
error: function (e) {
|
107
|
+
if(e.status == 502) {
|
108
|
+
$('#info_messages').append("<div class='alert alert-danger'><button type='button' class='close' data-dismiss='alert' aria-hidden='true'>×</button>Request timed out, try again later</div>");
|
109
|
+
} else {
|
110
|
+
$('#info_messages').append("<div class='alert alert-danger'><button type='button' class='close' data-dismiss='alert' aria-hidden='true'>×</button>"+e.responseJSON.error+"</div>");
|
111
|
+
}
|
112
|
+
$("#profile_help_block").hide();
|
113
|
+
}
|
114
|
+
});
|
115
|
+
|
116
|
+
});
|
117
|
+
|
118
|
+
$('#form-login').bind("submit", function(e){
|
119
|
+
e.preventDefault();
|
120
|
+
var val = $('#selfid-field')[0].value
|
121
|
+
if(val == null) {
|
122
|
+
return;
|
123
|
+
}
|
124
|
+
if (val.length != 11) {
|
125
|
+
$('#selfid-field').addClass("is-invalid")
|
126
|
+
return;
|
127
|
+
}
|
128
|
+
$('#selfid-field').removeClass("is-invalid")
|
129
|
+
$('#page-wrapper').addClass('page-loading');
|
130
|
+
$(this).blur();
|
131
|
+
|
132
|
+
//do some verification
|
133
|
+
$("#connection_id")[0].value = connectionID();
|
134
|
+
$("#process").removeClass("hidden");
|
135
|
+
$('#non-dl-container').addClass("hidden");
|
136
|
+
|
137
|
+
$.ajax({
|
138
|
+
type: 'POST',
|
139
|
+
url: $(this).context.action,
|
140
|
+
data: $(this).serialize()
|
141
|
+
});
|
142
|
+
});
|
143
|
+
|
144
|
+
};
|
145
|
+
|
146
|
+
$(function () {
|
147
|
+
updateQR()
|
148
|
+
})
|
149
|
+
|
150
|
+
|
@@ -0,0 +1,2 @@
|
|
1
|
+
/*! js-cookie v2.1.4 | MIT */
|
2
|
+
!function(a){var b=!1;if("function"==typeof define&&define.amd&&(define(a),b=!0),"object"==typeof exports&&(module.exports=a(),b=!0),!b){var c=window.Cookies,d=window.Cookies=a();d.noConflict=function(){return window.Cookies=c,d}}}(function(){function a(){for(var a=0,b={};a<arguments.length;a++){var c=arguments[a];for(var d in c)b[d]=c[d]}return b}function b(c){function d(b,e,f){var g;if("undefined"!=typeof document){if(arguments.length>1){if(f=a({path:"/"},d.defaults,f),"number"==typeof f.expires){var h=new Date;h.setMilliseconds(h.getMilliseconds()+864e5*f.expires),f.expires=h}f.expires=f.expires?f.expires.toUTCString():"";try{g=JSON.stringify(e),/^[\{\[]/.test(g)&&(e=g)}catch(p){}e=c.write?c.write(e,b):encodeURIComponent(e+"").replace(/%(23|24|26|2B|3A|3C|3E|3D|2F|3F|40|5B|5D|5E|60|7B|7D|7C)/g,decodeURIComponent),b=encodeURIComponent(b+""),b=b.replace(/%(23|24|26|2B|5E|60|7C)/g,decodeURIComponent),b=b.replace(/[\(\)]/g,escape);var i="";for(var j in f)f[j]&&(i+="; "+j,!0!==f[j]&&(i+="="+f[j]));return document.cookie=b+"="+e+i}b||(g={});for(var k=document.cookie?document.cookie.split("; "):[],l=0;l<k.length;l++){var m=k[l].split("="),n=m.slice(1).join("=");'"'===n.charAt(0)&&(n=n.slice(1,-1));try{var o=m[0].replace(/(%[0-9A-Z]{2})+/g,decodeURIComponent);if(n=c.read?c.read(n,o):c(n,o)||n.replace(/(%[0-9A-Z]{2})+/g,decodeURIComponent),this.json)try{n=JSON.parse(n)}catch(p){}if(b===o){g=n;break}b||(g[o]=n)}catch(p){}}return g}}return d.set=d,d.get=function(a){return d.call(d,a)},d.getJSON=function(){return d.apply({json:!0},[].slice.call(arguments))},d.defaults={},d.remove=function(b,c){d(b,"",a(c,{expires:-1}))},d.withConverter=b,d}return b(function(){})});
|
@@ -0,0 +1,8 @@
|
|
1
|
+
/*!
|
2
|
+
* ki.js v1.1.0 - 2015-10-06
|
3
|
+
* Copyright (c) 2015 Denis Ciccale (@tdecs)
|
4
|
+
* Released under MIT license
|
5
|
+
*/
|
6
|
+
!function(a,b,c,d){function e(c){b.push.apply(this,c&&c.nodeType?[c]:""+c===c?a.querySelectorAll(c):d)}$=function(b){return/^f/.test(typeof b)?/c/.test(a.readyState)?b():$(a).on("DOMContentLoaded",b):new e(b)},$[c]=e[c]=$.fn=e.fn={length:0,on:function(a,b){return this.each(function(c){c.addEventListener(a,b)})},off:function(a,b){return this.each(function(c){c.removeEventListener(a,b)})},each:function(a,c){return b.forEach.call(this,a,c),this},splice:b.splice}}(document,[],"prototype");
|
7
|
+
|
8
|
+
!function(){$.each=function(a,b){for(var c=0,d=a.length;d>c;++c)b.call(a[c],c,a[c]);return this};var a=["addClass","removeClass","toggleClass"],b=["add","remove","toggle"];a.forEach(function(a,c){$.prototype[a]=function(a){return this.each(function(d){d.classList[b[c]](a)})}}),$.prototype.hasClass=function(a){return this[0].classList.contains(a)},$.prototype.append=function(a){return this.each(function(b){b.appendChild(a[0])})},$.prototype.prepend=function(a){return this.each(function(b){b.insertBefore(a[0],b.firstChild)})},$.prototype.hide=function(){return this.each(function(a){a.style.display="none"})},$.prototype.show=function(){return this.each(function(a){a.style.display=""})},$.prototype.attr=function(a,b){return b===[]._?this[0].getAttribute(a):this.each(function(c){c.setAttribute(a,b)})},$.prototype.removeAttr=function(a){return this.each(function(b){b.removeAttribute(a)})},$.prototype.hasAttr=function(a){return this[0].hasAttribute(a)},$.prototype.before=function(a){return this.each(function(b){b.insertAdjacentHTML("beforebegin",a)})},$.prototype.after=function(a){return this.each(function(b){b.insertAdjacentHTML("afterend",a)})},$.prototype.css=function(a,b){if("object"==typeof a){for(var c in a)this.each(function(b){b.style[c]=a[c]});return this}return b===[]._?this[0].style[a]:this.each(function(c){c.style[a]=b})},$.prototype.first=function(){return $(this[0])},$.prototype.last=function(){return $(this[this.length-1])},$.prototype.get=function(a){return $(this[a])},$.prototype.text=function(a){return a===[]._?this[0].textContent:this.each(function(b){b.textContent=a})},$.prototype.html=function(a){return a===[]._?this[0].innerHTML:this.each(function(b){b.innerHTML=a})},$.prototype.parent=function(){return this.length<2?$(this[0].parentNode):[]},$.prototype.remove=function(){return this.each(function(a){a.parentNode.removeChild(a)})},$.trim=function(a){return a.replace(/^\s+|\s+$/g,"")},$.prototype.trigger=function(a){if(document.createEvent){var b=document.createEvent("HTMLEvents");b.initEvent(a,!0,!1),this.each(function(a){a.dispatchEvent(b)})}else this.each(function(b){b.fireEvent("on"+a)})},$.prototype.is=function(a){var b=this[0].matches||this[0].matchesSelector||this[0].msMatchesSelector||this[0].mozMatchesSelector||this[0].webkitMatchesSelector||this[0].oMatchesSelector;if(b)return b.call(this[0],a);for(var c=this[0].parentNode.querySelectorAll(a),d=c.length;d--;)if(c[d]===this[0])return!0;return!1},"filter map".split(" ").forEach(function(a){$[a]=function(b,c){return b[a](c)}}),$.stop=function(a){a.preventDefault?a.preventDefault():a.returnValue=!1},$.param=function(a,b){var c=[];for(var d in a){var e=b?b+"["+d+"]":d,f=a[d];c.push("object"==typeof f?$.param(f,e):encodeURIComponent(e)+"="+encodeURIComponent(f))}return c.join("&")},$.ajax=function(a,b,c){var d=new XMLHttpRequest,e=new $.Deferred,f="object"==typeof b?1:0,g=["GET","POST"];d.open(g[f],a,!0);var h=f?c:b;return"undefined"==typeof c&&"function"!=typeof b&&(h=function(){}),d.onerror=function(){e.reject(this),h(this,!0)},d.onreadystatechange=function(){4===this.readyState&&(this.status>=200&&this.status<400?(e.resolve(this.response),h(this.response,!0)):(e.reject(this),h(this,!0)))},f?(d.setRequestHeader("Content-type","application/x-www-form-urlencoded"),d.send($.param(b))):d.send(),d=null,e.promise()},function(a){function b(a){return"[object Array]"===Object.prototype.toString.call(a)}function c(a,c){if(b(a))for(var d=0;d<a.length;d++)c(a[d]);else c(a)}function d(a){var e="pending",f=[],g=[],h=[],i=[],j={done:function(){for(var a=0;a<arguments.length;a++)if(arguments[a])if(b(arguments[a]))for(var c=arguments[a],d=0;d<c.length;d++)"resolved"===e&&c[d].apply(this,i),f.push(c[d]);else"resolved"===e&&arguments[a].apply(this,i),f.push(arguments[a]);return this},fail:function(){for(var a=0;a<arguments.length;a++)if(arguments[a])if(b(arguments[a]))for(var c=arguments[a],d=0;d<c.length;d++)"rejected"===e&&c[d].apply(this,i),g.push(c[d]);else"rejected"===e&&arguments[a].apply(this,i),g.push(arguments[a]);return this},always:function(){return this.done.apply(this,arguments).fail.apply(this,arguments)},progress:function(){for(var a=0;a<arguments.length;a++)if(arguments[a])if(b(arguments[a]))for(var c=arguments[a],d=0;d<c.length;d++)"pending"===e&&h.push(c[d]);else"pending"===e&&h.push(arguments[a]);return this},then:function(){arguments.length>1&&arguments[1]&&this.fail(arguments[1]),arguments.length>0&&arguments[0]&&this.done(arguments[0]),arguments.length>2&&arguments[2]&&this.progress(arguments[2])},promise:function(a){if("undefined"==typeof a)return j;for(var b in j)a[b]=j[b];return a},state:function(){return e},debug:function(){console.log("[debug]",f,g,e)},isRejected:function(){return"rejected"===e},isResolved:function(){return"resolved"===e},pipe:function(a,b,e){return d(function(d){c(a,function(a){"function"==typeof a?k.done(function(){var b=a.apply(this,arguments);b&&"function"==typeof b?b.promise().then(d.resolve,d.reject,d.notify):d.resolve(b)}):k.done(d.resolve)}),c(b,function(a){"function"==typeof a?k.fail(function(){var b=a.apply(this,arguments);b&&"function"==typeof b?b.promise().then(d.resolve,d.reject,d.notify):d.reject(b)}):k.fail(d.reject)})}).promise()}},k={resolveWith:function(a){if("pending"===e){e="resolved";for(var b=i=arguments.length>1?arguments[1]:[],c=0;c<f.length;c++)f[c].apply(a,b)}return this},rejectWith:function(a){if("pending"===e){e="rejected";for(var b=i=arguments.length>1?arguments[1]:[],c=0;c<g.length;c++)g[c].apply(a,b)}return this},notifyWith:function(a){if("pending"===e)for(var b=i=arguments.length>1?arguments[1]:[],c=0;c<h.length;c++)h[c].apply(a,b);return this},resolve:function(){return this.resolveWith(this,arguments)},reject:function(){return this.rejectWith(this,arguments)},notify:function(){return this.notifyWith(this,arguments)}},l=j.promise(k);return a&&a.apply(l,[l]),l}var e=function(){if(arguments.length<2){var a=arguments.length?arguments[0]:void 0;return a&&"function"==typeof a.isResolved&&"function"==typeof a.isRejected?a.promise():d().resolve(a).promise()}return function(a){for(var b=d(),c=a.length,e=0,f=new Array(c),g=0;g<a.length;g++)!function(d){var g=null;a[d].done?a[d].done(function(){f[d]=arguments.length<2?arguments[0]:arguments,++e==c&&b.resolve.apply(b,f)}).fail(function(){b.reject(arguments)}):(g=a[d],a[d]=new Deferred,a[d].done(function(){f[d]=arguments.length<2?arguments[0]:arguments,++e==c&&b.resolve.apply(b,f)}).fail(function(){b.reject(arguments)}).resolve(g))}(g);return b.promise()}(arguments)};a.Deferred=d,a.when=d.when=e}($)}();
|
@@ -0,0 +1,27 @@
|
|
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
|
+
|
17
|
+
#auth-ok {
|
18
|
+
position: absolute;
|
19
|
+
padding-left: 190px;
|
20
|
+
padding-top: 110px;
|
21
|
+
font-size: 100px;
|
22
|
+
display: none;
|
23
|
+
}
|
24
|
+
|
25
|
+
div.hidden {
|
26
|
+
display: none;
|
27
|
+
}
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module ApplicationCable
|
2
|
+
class Connection < ActionCable::Connection::Base
|
3
|
+
identified_by :current_user
|
4
|
+
|
5
|
+
def connect
|
6
|
+
self.current_user = find_verified_user
|
7
|
+
end
|
8
|
+
|
9
|
+
protected
|
10
|
+
|
11
|
+
def find_verified_user
|
12
|
+
SecureRandom.urlsafe_base64
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
module SelfAuthRails
|
2
|
+
class SessionsController < ::ApplicationController
|
3
|
+
def new; end
|
4
|
+
|
5
|
+
# Authenticates a user with the given token
|
6
|
+
def create
|
7
|
+
if session_params[:token].empty? # AJAX request
|
8
|
+
SelfAuthRails.self_client.facts.request(session_params[:selfid],
|
9
|
+
[:display_name],
|
10
|
+
auth: true,
|
11
|
+
cid: session_params[:connection_id],
|
12
|
+
async: true)
|
13
|
+
|
14
|
+
request.format = :json
|
15
|
+
respond_to do |format|
|
16
|
+
format.json { head :no_content }
|
17
|
+
end
|
18
|
+
else # Form submission
|
19
|
+
reset_user_token(session_params[:token])
|
20
|
+
respond_to do |format|
|
21
|
+
format.html { redirect_to SelfAuthRails.authenticated_path, notice: 'Welcome.' }
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# Logs out the current user.
|
27
|
+
def logout
|
28
|
+
session[:user_id] = nil
|
29
|
+
|
30
|
+
respond_to do |format|
|
31
|
+
format.html { redirect_to new_url }
|
32
|
+
format.json { head :no_content }
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# Generates a QR code for authenticating users.
|
37
|
+
def qr
|
38
|
+
if Rails.env.test?
|
39
|
+
send_data("test")
|
40
|
+
else
|
41
|
+
uuid = "qr::#{params[:uuid]}"
|
42
|
+
|
43
|
+
img = ::SelfClient.authentication.generate_qr(
|
44
|
+
facts: SelfAuthRails.auth_facts,
|
45
|
+
cid: uuid,
|
46
|
+
exp_timeout: 86_400
|
47
|
+
)
|
48
|
+
|
49
|
+
send_data(img.as_png(border: 0, size: 400),
|
50
|
+
type: 'image/png',
|
51
|
+
disposition: 'inline')
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# Generates a dynamic link to authenticate users.
|
56
|
+
def dl
|
57
|
+
link = '#'
|
58
|
+
unless Rails.env.test?
|
59
|
+
uuid = "dl::#{params[:uuid]}"
|
60
|
+
link = SelfAuthRails.self_client.facts.generate_deep_link(SelfAuthRails.auth_facts,
|
61
|
+
SelfAuthRails.authenticated_path,
|
62
|
+
cid: uuid,
|
63
|
+
auth: true)
|
64
|
+
end
|
65
|
+
render json: { url: link }
|
66
|
+
end
|
67
|
+
|
68
|
+
protected
|
69
|
+
|
70
|
+
# Never trust parameters from the scary internet, only allow the white list through.
|
71
|
+
def session_params
|
72
|
+
params.permit(:selfid, :token, :connection_id, :qr_url, :dl_url, :authenticity_token)
|
73
|
+
end
|
74
|
+
|
75
|
+
def auto_join_authenticated_users
|
76
|
+
redirect_to SelfAuthRails.authenticated_path unless session[:user_id].nil?
|
77
|
+
end
|
78
|
+
|
79
|
+
def reset_user_token(token)
|
80
|
+
@user = SelfAuthRails.session_class.find_by(token: token)
|
81
|
+
if @user.nil? # The user is already created on a different tab
|
82
|
+
@user = helpers.current_user
|
83
|
+
else
|
84
|
+
@user.token = ''
|
85
|
+
@user.save!
|
86
|
+
session[:user_id] = @user.id.to_s
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module SelfAuthRails
|
2
|
+
module ApplicationHelper
|
3
|
+
def authenticate_user!
|
4
|
+
current_url = request.base_url + request.path
|
5
|
+
|
6
|
+
uri = URI(main_app.root_url)
|
7
|
+
|
8
|
+
new_url = SelfAuthRails::Engine.routes.url_helpers.new_url(host: uri.host, port: uri.port)
|
9
|
+
create_url = SelfAuthRails::Engine.routes.url_helpers.create_url(host: uri.host, port: uri.port)
|
10
|
+
qr_url = SelfAuthRails::Engine.routes.url_helpers.qr_url(host: uri.host, port: uri.port)
|
11
|
+
dl_url = SelfAuthRails::Engine.routes.url_helpers.dl_url(host: uri.host, port: uri.port)
|
12
|
+
|
13
|
+
if [new_url, create_url, qr_url, dl_url].include? current_url
|
14
|
+
redirect_to root_url unless current_user.nil?
|
15
|
+
else
|
16
|
+
redirect_to new_url if current_user.nil?
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def current_user
|
21
|
+
SelfAuthRails.session_class.find_by(id: session[:user_id])
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# Manages self authentication responses
|
2
|
+
class SelfAuthResponseManagerService
|
3
|
+
STATUS_ERR = 'errored'.freeze
|
4
|
+
STATUS_REJ = 'rejected'.freeze
|
5
|
+
STATUS_OK = 'accepted'.freeze
|
6
|
+
STATUS_COM = 'completed'.freeze
|
7
|
+
STATUS_RECIEVED = 'received'.freeze
|
8
|
+
|
9
|
+
def initialize(broadcaster)
|
10
|
+
@broadcaster = broadcaster
|
11
|
+
end
|
12
|
+
|
13
|
+
# Processes a Self authentication response
|
14
|
+
def process_response(self_auth_response)
|
15
|
+
return if self_auth_response.nil?
|
16
|
+
|
17
|
+
cid = self_auth_response.id.split('_').first.split('::').last
|
18
|
+
channel = "conversation_#{cid}_channel"
|
19
|
+
|
20
|
+
if self_auth_response.errored?
|
21
|
+
broadcast_status_change channel, STATUS_ERR, message: 'A timeout ocured while waiting for your device response'
|
22
|
+
return
|
23
|
+
end
|
24
|
+
|
25
|
+
if self_auth_response.rejected?
|
26
|
+
broadcast_status_change channel, STATUS_REJ
|
27
|
+
return
|
28
|
+
end
|
29
|
+
|
30
|
+
unless admin?(self_auth_response.from)
|
31
|
+
broadcast_status_change channel, STATUS_ERR, message: 'You are forbidden to access this service'
|
32
|
+
return
|
33
|
+
end
|
34
|
+
|
35
|
+
user = set_user_token(self_auth_response.from, self_auth_response)
|
36
|
+
broadcast_status_change channel, STATUS_COM, token: user[:token]
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
# creates or updates a user setting a random token.
|
42
|
+
def set_user_token(selfid, self_auth_response)
|
43
|
+
user = {
|
44
|
+
token: SecureRandom.uuid,
|
45
|
+
selfid: selfid,
|
46
|
+
facts: {}
|
47
|
+
}
|
48
|
+
|
49
|
+
SelfAuthRails.auth_facts.each do |fact|
|
50
|
+
user[:facts][fact] = self_auth_response.attestation_values_for(fact).first
|
51
|
+
end
|
52
|
+
|
53
|
+
u = SelfAuthRails.session_class.find_by(selfid: user[:selfid])
|
54
|
+
u = SelfAuthRails.session_class.new(selfid: user[:selfid]) if u.nil?
|
55
|
+
u.token = user[:token]
|
56
|
+
SelfAuthRails.fact_mapping.each do |fact_name, field|
|
57
|
+
u.send("#{field}=", user[:facts][fact_name.to_sym])
|
58
|
+
end
|
59
|
+
u.save!
|
60
|
+
|
61
|
+
user
|
62
|
+
end
|
63
|
+
|
64
|
+
# broadcasts a status change through websockets to the web client
|
65
|
+
def broadcast_status_change(channel, status, opts = {})
|
66
|
+
token = opts.fetch(:token, '')
|
67
|
+
message = opts.fetch(:message, '')
|
68
|
+
type = opts.fetch(:type, 'auth')
|
69
|
+
@broadcaster.broadcast channel, { status: status, token: token, message: message, type: type }
|
70
|
+
end
|
71
|
+
|
72
|
+
# checks if the given id is in the admins list
|
73
|
+
def admin?(id)
|
74
|
+
ENV['ADMIN_IDS'].split(',').include? id
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
<%= hidden_field_tag "conversation_id", "", id: "conversation_id" %>
|
2
|
+
<%= form_with(model: @user, url: create_path, local: true, id: "form-login", class: "form-horizontal form-bordered form-control-borderless") do |form| %>
|
3
|
+
<%= form.hidden_field 'qr_url', id: 'qr_url', value: qr_path %>
|
4
|
+
<%= form.hidden_field 'dl_url', id: 'dl_url', value: dl_path %>
|
5
|
+
<%= form.hidden_field 'connection_id', id: "connection_id" %>
|
6
|
+
<%= form.hidden_field :token, id: "login-token" %>
|
7
|
+
|
8
|
+
<div id="app">
|
9
|
+
<section class="section">
|
10
|
+
<div class="container mt-5">
|
11
|
+
<div class="row">
|
12
|
+
<div class="col-12 col-sm-8 offset-sm-2 col-md-6 offset-md-3 col-lg-6 offset-lg-3 col-xl-6 offset-xl-3">
|
13
|
+
<div class="card card-primary">
|
14
|
+
<div class="card-body">
|
15
|
+
|
16
|
+
<div id="dl-container" class="hidden">
|
17
|
+
<div class="col-xs-12">
|
18
|
+
<h4>Welcome to Self</h4>
|
19
|
+
<p>Please click the button below to authenticate using your Self App</p>
|
20
|
+
</div>
|
21
|
+
<div class="text-center form-group" style="border:0px; background-color: #f4f4f4; padding-top:20px; padding-bottom:20px">
|
22
|
+
<a href="#" class='btn btn-success' id="dl">Authenticate with <strong>Self</strong></a>
|
23
|
+
<button id="switch-not-dl" class="btn btn-info"><i class="fa fa-qrcode"></i> </button>
|
24
|
+
</div>
|
25
|
+
</div>
|
26
|
+
|
27
|
+
<div id="non-dl-container">
|
28
|
+
<div id="qr-container" class='text-center form-group' style="border:0px;">
|
29
|
+
<div class="qr-wrapper" style='min-height:400px'>
|
30
|
+
<div id="auth-ok">
|
31
|
+
<i class="fa fa-check text-success"></i>
|
32
|
+
</div>
|
33
|
+
|
34
|
+
<img src="" id="qr" style="max-width:400px;max-height:400px;width: inherit;height: inherit;">
|
35
|
+
</div>
|
36
|
+
</div>
|
37
|
+
|
38
|
+
<div id='login-form-group' class="form-group" style="padding: 0 25px 0 25px;">
|
39
|
+
<div class="input-group mb-3">
|
40
|
+
<div class="input-group-prepend">
|
41
|
+
<div class="input-group-text">
|
42
|
+
<i class="fas fa-user"></i>
|
43
|
+
</div>
|
44
|
+
</div>
|
45
|
+
<%= form.text_field :selfid, placeholder: "Self identifier", class: "form-control input-lg", id: "selfid-field", required: true %>
|
46
|
+
<div class="invalid-feedback invalid-pad">Oh no! Self identifier does not exist.</div>
|
47
|
+
<div class="input-group-append">
|
48
|
+
<button id="sign-in" class="btn btn-primary"><i class="fa fa-angle-right"></i> Sign in</button>
|
49
|
+
</div>
|
50
|
+
</div>
|
51
|
+
</div>
|
52
|
+
|
53
|
+
|
54
|
+
<div class="form-group form-actions login-new-identifier">
|
55
|
+
<div class="row justify-content-md-center">
|
56
|
+
<div class="col">
|
57
|
+
<small>Don't have a Self Identifier?</small><br />
|
58
|
+
<a href="javascript:void(0)" id="link-register-login"><small class="text-info">Download the Self app and create a Self identifier</small></a>
|
59
|
+
</div>
|
60
|
+
<div class="col col-lg-4 text-right" style="padding-top:10px;">
|
61
|
+
<button class="btn btn-sm btn-info"><i class="fa fa-angle-right"></i> Get the app</button>
|
62
|
+
</div>
|
63
|
+
</div>
|
64
|
+
|
65
|
+
</div>
|
66
|
+
</div>
|
67
|
+
|
68
|
+
</div>
|
69
|
+
</div>
|
70
|
+
</div>
|
71
|
+
</div>
|
72
|
+
</div>
|
73
|
+
</section>
|
74
|
+
</div>
|
75
|
+
<% end %>
|
data/config/routes.rb
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
SelfAuthRails::Engine.routes.draw do
|
2
|
+
get 'new', to: 'sessions#new'
|
3
|
+
post 'create', to: 'sessions#create'
|
4
|
+
get 'qr', to: 'sessions#qr'
|
5
|
+
get 'dl', to: 'sessions#dl'
|
6
|
+
get 'logout', to: 'sessions#logout'
|
7
|
+
|
8
|
+
# Serve websocket cable requests in-process
|
9
|
+
mount ActionCable.server => '/auth'
|
10
|
+
end
|
11
|
+
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module SelfAuthRails
|
2
|
+
class Engine < ::Rails::Engine
|
3
|
+
isolate_namespace SelfAuthRails
|
4
|
+
|
5
|
+
initializer "self-auth-rails.helpers" do
|
6
|
+
ActiveSupport.on_load(:action_controller_base) do
|
7
|
+
helper SelfAuthRails::Engine.helpers
|
8
|
+
include SelfAuthRails::Engine.helpers
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
initializer "self-auth-rails.assets.precompile" do |app|
|
13
|
+
app.config.assets.precompile << "manifest.js"
|
14
|
+
app.config.assets.precompile << "application.css"
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require "self-auth-rails/version"
|
2
|
+
require "self-auth-rails/engine"
|
3
|
+
|
4
|
+
module SelfAuthRails
|
5
|
+
mattr_accessor :self_client,
|
6
|
+
:session_class_name,
|
7
|
+
:auth_facts,
|
8
|
+
:fact_mapping,
|
9
|
+
:authenticated_path,
|
10
|
+
:authenticated_path
|
11
|
+
|
12
|
+
class Engine < ::Rails::Engine
|
13
|
+
default_auth_facts = [:display_name]
|
14
|
+
default_authenticated_path = '/'
|
15
|
+
|
16
|
+
config.after_initialize do
|
17
|
+
unless SelfAuthRails::self_client.nil?
|
18
|
+
SelfAuthRails.auth_facts ||= default_auth_facts
|
19
|
+
SelfAuthRails.authenticated_path ||= default_authenticated_path
|
20
|
+
|
21
|
+
response_manager = SelfAuthResponseManagerService.new(ActionCable.server)
|
22
|
+
|
23
|
+
# Subscribe to fact responses
|
24
|
+
SelfAuthRails::self_client.facts.subscribe do |auth|
|
25
|
+
response_manager.process_response(auth)
|
26
|
+
end
|
27
|
+
|
28
|
+
# Subscribe to authentication responses
|
29
|
+
SelfAuthRails::self_client.authentication.subscribe do |auth|
|
30
|
+
response_manager.process_response(auth)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.session_class
|
37
|
+
class_name = session_class_name || 'User'
|
38
|
+
Object.const_get(class_name)
|
39
|
+
end
|
40
|
+
|
41
|
+
# this function maps the vars from your app into your engine
|
42
|
+
def self.setup(&block)
|
43
|
+
yield self
|
44
|
+
end
|
45
|
+
|
46
|
+
def authenticated_path
|
47
|
+
SelfAuthRails::authenticated_path || SelfAuthRails::default_authenticated_path
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'selfsdk'
|
2
|
+
|
3
|
+
if defined?(Rails::Server) && !defined?(::SelfClient)
|
4
|
+
# If self client is already initialized skip this initialization
|
5
|
+
return unless defined?(::SelfClient).nil?
|
6
|
+
|
7
|
+
# Initializes self client
|
8
|
+
::SelfClient = SelfSDK::App.new(
|
9
|
+
ENV['SELF_APP_ID'],
|
10
|
+
ENV['SELF_APP_KEY'],
|
11
|
+
ENV['SELF_STORAGE_KEY'],
|
12
|
+
ENV['SELF_STORAGE_DIR'],
|
13
|
+
env: ENV['SELF_ENVIRONMENT'],
|
14
|
+
)
|
15
|
+
|
16
|
+
# Setup self-auth-rails engine
|
17
|
+
SelfAuthRails.setup do |config|
|
18
|
+
# Initializes the self-ruby-sdk client.
|
19
|
+
config.self_client = ::SelfClient
|
20
|
+
|
21
|
+
# Sets rails logger as default self-ruby-sdk logger (optional).
|
22
|
+
SelfSDK.logger = Rails.logger
|
23
|
+
|
24
|
+
# Defines the facts we're going to require for a user to authenticate.
|
25
|
+
# If not provided it will default to [:display_name].
|
26
|
+
config.auth_facts = [:display_name, :email_address]
|
27
|
+
|
28
|
+
# Defines the model class name to be used for persistance. The active
|
29
|
+
# model provided must have `selfid` and `token` attributes.
|
30
|
+
# Note this config entry is optional and defaults to `User`.
|
31
|
+
config.session_class_name = 'User'
|
32
|
+
|
33
|
+
# Defines the path to redirect the user when authentication succeeds.
|
34
|
+
# Optional entry defaulting to '/'.
|
35
|
+
config.authenticated_path = '/'
|
36
|
+
|
37
|
+
# In case you want to persist the authentication returning facts, you
|
38
|
+
# can provide a map for the fact_name and the ActiveModel object property.
|
39
|
+
#
|
40
|
+
# This is an optional entry and you only need to provide it if you're intending
|
41
|
+
# to request and store facts during the authentication process with
|
42
|
+
# config.auth_facts
|
43
|
+
config.fact_mapping = { display_name: :name }
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# desc "Explaining what the task does"
|
2
|
+
# task :self-auth-rails do
|
3
|
+
# # Task goes here
|
4
|
+
# end
|
5
|
+
|
6
|
+
desc 'Creates the self-auth-rails initializer on your application'
|
7
|
+
namespace :self_auth_rails do
|
8
|
+
task :init do
|
9
|
+
# Getting model to be used as authentication
|
10
|
+
puts "What model do you want to use for authentication? (Defaults to \"User\")"
|
11
|
+
model_name = STDIN.gets.strip
|
12
|
+
model_name = 'User' if model_name.empty?
|
13
|
+
|
14
|
+
log 'Generating migrations'
|
15
|
+
if Object.const_defined? model_name
|
16
|
+
log "#{model_name} already initialized, adding necessary columns"
|
17
|
+
system "SKIP_SELF=1 bin/rails g migration add_auth_to_#{model_name.downcase}s selfid:string name:string token:string"
|
18
|
+
else
|
19
|
+
system "SKIP_SELF=1 bin/rails g model #{model_name.downcase} selfid:string name:string token:string"
|
20
|
+
end
|
21
|
+
log 'migration file created!'
|
22
|
+
|
23
|
+
# Adding initializer to your application
|
24
|
+
log 'Creating self-auth-rails initializer from template'
|
25
|
+
body = File.read(__FILE__.gsub('self_auth_rails_tasks.rake', 'initializer.rb.tpl'))
|
26
|
+
body.gsub!("{{AuthModel}}", model_name)
|
27
|
+
File.open("#{Dir.pwd}/config/initializers/self_auth_rails.rb", 'w') { |file| file << body }
|
28
|
+
log 'config/initializers/self_auth_rails.rb created'
|
29
|
+
|
30
|
+
# Adding routes to your application
|
31
|
+
puts "What alias do you want to use to mount this rails engine? (Defaults to \"auth\")"
|
32
|
+
as = STDIN.gets.strip
|
33
|
+
as = 'auth' if as.empty?
|
34
|
+
|
35
|
+
log 'Adding routes to config/routes.rb'
|
36
|
+
file_path = "#{Dir.pwd}/config/routes.rb"
|
37
|
+
body = File.read(file_path)
|
38
|
+
if body.include? 'SelfAuthRails'
|
39
|
+
log 'skipping... (self-auth-rails routes already added)'
|
40
|
+
else
|
41
|
+
pattern = 'Rails.application.routes.draw do'
|
42
|
+
content = body.gsub(pattern, "#{pattern}\n # Mounting self-auth-rails engine\n mount SelfAuthRails::Engine => \"/auth\", as: \"#{as}\"")
|
43
|
+
File.open(file_path, 'w') { |file| file << content }
|
44
|
+
log 'self-auth-rails routes added'
|
45
|
+
end
|
46
|
+
|
47
|
+
puts 'Adding assets headers'
|
48
|
+
assets_file_path = "#{Dir.pwd}/app/views/layouts/application.html.erb"
|
49
|
+
body = File.read(assets_file_path)
|
50
|
+
header1 = '<%= javascript_include_tag "self-auth-rails/manifest" %>'
|
51
|
+
header2 = '<%= stylesheet_link_tag "self-auth-rails/application.css" %>'
|
52
|
+
body.gsub!("</head>", "#{header1}\n\t#{header2}\n\t</head>")
|
53
|
+
File.open(assets_file_path, 'w') { |file| file << body }
|
54
|
+
log 'asset headers set'
|
55
|
+
|
56
|
+
manifest_file_path = "#{Dir.pwd}/app/assets/config/manifest.js"
|
57
|
+
body = File.read(manifest_file_path)
|
58
|
+
body += "\n//= link self-auth-rails/manifest.js"
|
59
|
+
File.open(manifest_file_path, 'w') { |file| file << body }
|
60
|
+
|
61
|
+
log 'All set!'
|
62
|
+
puts 'Remember to run `bin/rails db:migrate` to create your authentication table'
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def log(str)
|
67
|
+
str = "[self_auth_rails::init] #{str}"
|
68
|
+
puts "\e[#{36}m#{str}\e[0m"
|
69
|
+
end
|
metadata
ADDED
@@ -0,0 +1,116 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: self-auth-rails
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Adrià Cidre
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2022-08-11 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rails
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '7.0'
|
20
|
+
- - ">="
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 7.0.2.3
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - "~>"
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '7.0'
|
30
|
+
- - ">="
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 7.0.2.3
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: selfsdk
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - "~>"
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '0.0'
|
40
|
+
- - ">="
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: 0.0.200
|
43
|
+
type: :runtime
|
44
|
+
prerelease: false
|
45
|
+
version_requirements: !ruby/object:Gem::Requirement
|
46
|
+
requirements:
|
47
|
+
- - "~>"
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: '0.0'
|
50
|
+
- - ">="
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: 0.0.200
|
53
|
+
description: Provides an out of the box implementation for self authentication
|
54
|
+
email:
|
55
|
+
- 593270+adriacidre@users.noreply.github.com
|
56
|
+
executables: []
|
57
|
+
extensions: []
|
58
|
+
extra_rdoc_files: []
|
59
|
+
files:
|
60
|
+
- MIT-LICENSE
|
61
|
+
- README.md
|
62
|
+
- Rakefile
|
63
|
+
- app/assets/config/self-auth-rails/app.js
|
64
|
+
- app/assets/config/self-auth-rails/cable.js
|
65
|
+
- app/assets/config/self-auth-rails/channels/auth.js
|
66
|
+
- app/assets/config/self-auth-rails/channels/messages_channel.js
|
67
|
+
- app/assets/config/self-auth-rails/cookies.js
|
68
|
+
- app/assets/config/self-auth-rails/ki.js
|
69
|
+
- app/assets/config/self-auth-rails/manifest.js
|
70
|
+
- app/assets/stylesheets/self-auth-rails/application.css
|
71
|
+
- app/channels/application_cable/channel.rb
|
72
|
+
- app/channels/application_cable/connection.rb
|
73
|
+
- app/channels/messages_channel.rb
|
74
|
+
- app/controllers/self_auth_rails/application_controller.rb
|
75
|
+
- app/controllers/self_auth_rails/sessions_controller.rb
|
76
|
+
- app/helpers/self_auth_rails/application_helper.rb
|
77
|
+
- app/helpers/self_auth_rails/sessions_helper.rb
|
78
|
+
- app/jobs/self_auth_rails/application_job.rb
|
79
|
+
- app/mailers/self_auth_rails/application_mailer.rb
|
80
|
+
- app/models/self_auth_rails/application_record.rb
|
81
|
+
- app/services/self_auth_response_manager_service.rb
|
82
|
+
- app/views/self_auth_rails/sessions/create.html.erb
|
83
|
+
- app/views/self_auth_rails/sessions/new.html.erb
|
84
|
+
- config/routes.rb
|
85
|
+
- lib/self-auth-rails.rb
|
86
|
+
- lib/self-auth-rails/engine.rb
|
87
|
+
- lib/self-auth-rails/version.rb
|
88
|
+
- lib/tasks/initializer.rb.tpl
|
89
|
+
- lib/tasks/self_auth_rails_tasks.rake
|
90
|
+
homepage: https://joinself.com
|
91
|
+
licenses:
|
92
|
+
- MIT
|
93
|
+
metadata:
|
94
|
+
homepage_uri: https://joinself.com
|
95
|
+
source_code_uri: https://joinself.com
|
96
|
+
changelog_uri: https://joinself.com/changelog
|
97
|
+
post_install_message:
|
98
|
+
rdoc_options: []
|
99
|
+
require_paths:
|
100
|
+
- lib
|
101
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
102
|
+
requirements:
|
103
|
+
- - ">="
|
104
|
+
- !ruby/object:Gem::Version
|
105
|
+
version: '0'
|
106
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
requirements: []
|
112
|
+
rubygems_version: 3.1.6
|
113
|
+
signing_key:
|
114
|
+
specification_version: 4
|
115
|
+
summary: Joinself rails authentication gem
|
116
|
+
test_files: []
|