self-auth-rails 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|