twproxy 0.0.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: d76c0ff8f096bc96a9a2bb037879a31fd08f958f
4
+ data.tar.gz: e56c25cac077859244f4e062af7edcc59df27b4c
5
+ SHA512:
6
+ metadata.gz: fe6b63a884f567df715720db6b1ee556b91ae947e0e22def49b3911c70a2dee0851054a73478e8392a45fc3785ac8e5db466968101bb09afb3f8c48c7276c08e
7
+ data.tar.gz: ba4b836a56c8316719a53a456e6bf59c3f468e681337e7289e4aae42fec1254a950def4d3d1b7dbb65ccacf10950ecc35b50778e59336be47ab37607a9f38a4e
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source "https://rubygems.org"
2
+
3
+ gem "sinatra"
4
+ gem "haml"
5
+ gem "rotp"
6
+
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ The MIT License (MIT)
2
+ Copyright (c) 2016 Steve Gattuso
3
+
4
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
5
+ this software and associated documentation files (the "Software"), to deal in
6
+ the Software without restriction, including without limitation the rights to
7
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
8
+ of the Software, and to permit persons to whom the Software is furnished to do
9
+ so, subject to the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be included in all
12
+ copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,117 @@
1
+ # TiddlyWiki Proxy
2
+ *An authenticated proxy for protecting your wiki.*
3
+
4
+ ![Screencast](http://i.imgur.com/NmGf4iE.gif)
5
+
6
+ This gem provides an easy way to secure a TiddlyWiki installation so you can
7
+ expose it to the internet without having to worry about others gaining access.
8
+ The proxy provides you with a username, password and optionally a 2-factor auth
9
+ code (based on TOTP, which is compatible with [Google Authenticator](https://support.google.com/accounts/answer/1066447?hl=en)).
10
+
11
+ Although this proxy can be used as a server in itself, it's highly recommended
12
+ that you run it behind a proper webserver (such as nginx's reverse proxy) that
13
+ is secured with SSL.
14
+
15
+ ## Usage reference
16
+ Installation:
17
+ ```
18
+ $ gem install twproxy
19
+ ```
20
+
21
+ Argument reference:
22
+ ```
23
+ $ twproxy --help
24
+ Usage: twproxy [options]
25
+ -p, --port PORT Specify port to run the proxy on. Defaults to 8888.
26
+ -b, --bind HOSTNAME Specify hostname to bind to. Defaults to 127.0.0.1.
27
+ -s, --enable-ssl Ensures cookies are marked as secure.
28
+ -d, --destination URL Specify the url of the TiddlyWiki server. Defaults to http://localhost:8080.
29
+ -g CLEARTEXT, Generates a SHA1 hashed password
30
+ --generate-password
31
+ -u, --username USER Sets the username. Defaults to user.
32
+ -P, --password HASHED Sets the user's password. Use a SHA1 hash or -g. Defaults to test.
33
+ -a, --auth KEY Sets a TOTP key for use with Google Authenticator.
34
+ -h, --help Displays this help
35
+ ```
36
+
37
+ ## Basic Usage
38
+ Let's say we have a
39
+ [tiddlywiki server](http://tiddlywiki.com/static/Using%2520TiddlyWiki%2520on%2520Node.js.html)
40
+ running on `http://localhost:8080` that we'd like to protect with twproxy.
41
+ Fistly we'll want to generate a hashed version of the password we'd like to use:
42
+
43
+ ```
44
+ $ twproxy -g helloWorld
45
+ 5395ebfd174b0a5617e6f409dfbb3e064e3fdf0a
46
+ ```
47
+
48
+ Great! Now let's spin up twproxy so we can start working on our wiki,
49
+ protecting it with a username of `stevenleeg` and a password of `helloWorld`:
50
+
51
+ ```
52
+ $ twproxy -u stevenleeg -P 5395ebfd174b0a5617e6f409dfbb3e064e3fdf0a -d http://localhost:8080/
53
+ ```
54
+
55
+ If all goes well you can now navigate to `http://localhost:8888` and see a
56
+ prompt for your username and password.
57
+
58
+ That's all there is to it! Twproxy is set up and ready to go for basic usage,
59
+ however if you're paranoid I'd recommend further securing your wiki with SSL
60
+ (using an nginx reverse proxy) and 2FA.
61
+
62
+ ## Enabling 2FA
63
+ Sometimes a username and password isn't enough for paranoid minds. Not to
64
+ worry! Twproxy lets you add another layer of security onto your wiki by
65
+ supporting time-based one-time password authentication. This allows you to also
66
+ require a unique one-time code from Google Authenticator (or any other TOTP app)
67
+ in order to sign in.
68
+
69
+ Setting up 2FA requires acquiring a secret key that is used to generate one-time
70
+ passwords. [This online generator](http://www.xanxys.net/totp/) will do the job,
71
+ but if you're serious about security you should know never to trust an online
72
+ generator of secret keys.
73
+
74
+ For this example use case I used the online generator to get a key (note, I
75
+ copied the base32 version of the secret key):
76
+ `o76swdmjl74izl7bihmziod333cwnje3`.
77
+
78
+ I can then add 2FA to our previous example by adding the `-a` flag:
79
+ ```
80
+ $ twproxy -u stevenleeg -P 5395ebfd174b0a5617e6f409dfbb3e064e3fdf0a -d http://localhost:8080/ -a o76swdmjl74izl7bihmziod333cwnje3
81
+ ```
82
+
83
+ To test it, scan the QR code on the secret key generator's site into your
84
+ favorite TOTP app and try logging in (the authentication screen will now show a
85
+ third box for an auth code).
86
+
87
+ If all goes well you should only be able to sign in after entering the correct
88
+ auth code generated on your phone.
89
+
90
+ ## Enabling SSL
91
+ If you plan on exposing your wiki to the internet, it's a smart idea to protect
92
+ it by running it over an encrypted HTTPS connection. The easiest way to do this
93
+ is by using an [nginx reverse proxy](https://www.nginx.com/resources/admin-guide/reverse-proxy/)
94
+ paired with an SSL certificate from [Let's Encrypt](https://letsencrypt.org/).
95
+
96
+ Getting those two set up is out of scope of this readme, however it may be
97
+ helpful to check out DigitalOcean's [SSL installation guide](https://www.digitalocean.com/community/tutorials/how-to-create-an-ssl-certificate-on-nginx-for-ubuntu-14-04) if you're lost.
98
+
99
+ An important note for those attempting to run twproxy over an SSL connection:
100
+ you must add the `--enable-ssl` flag when starting the proxy! Without this flag twproxy
101
+ won't mark cookies as secure, leaving you confused each time you log in
102
+ correctly only to see yet another authentication prompt.
103
+
104
+ ## Using with Docker
105
+ **Note:** More documentation on this to come. This is still a WIP.
106
+
107
+ ## Issues
108
+ If you're having trouble with twproxy please don't hesitate to open an issue to
109
+ let me know. I'm actively maintaining this project for my own use, so it's in
110
+ my best interest to make sure it is secure and functional.
111
+
112
+ If you are filing a bug report please make sure you describe all steps
113
+ necessary to reproduce the issue. This will make my job much easier and make
114
+ sure your bug gets fixed faster.
115
+
116
+ Happy wiki-ing!
117
+
data/bin/twproxy ADDED
@@ -0,0 +1,54 @@
1
+ #!/usr/bin/env ruby
2
+ require "twproxy"
3
+ require "sinatra"
4
+ require "optparse"
5
+ require "digest/sha1"
6
+
7
+ options = {}
8
+ OptionParser.new do |opts|
9
+ opts.banner = "Usage: twproxy [options]"
10
+
11
+ opts.on("-p", "--port PORT", "Specify port to run the proxy on. Defaults to 8888.") do |port|
12
+ options[:port] = port
13
+ end
14
+ opts.on("-b", "--bind HOSTNAME", "Specify hostname to bind to. Defaults to 127.0.0.1.") do |hostname|
15
+ options[:bind] = hostname
16
+ end
17
+ opts.on("-s", "--enable-ssl", "Ensures cookies are marked as secure.") do |ssl|
18
+ options[:enable_ssl] = ssl
19
+ end
20
+ opts.on("-d", "--destination URL", "Specify the url of the TiddlyWiki server. Defaults to http://localhost:8080.") do |url|
21
+ options[:url] = url
22
+ end
23
+ opts.on("-g", "--generate-password CLEARTEXT", "Generates a SHA1 hashed password") do |pass|
24
+ puts Digest::SHA1.hexdigest(pass)
25
+ exit
26
+ end
27
+ opts.on("-u", "--username USER", "Sets the username. Defaults to user.") do |user|
28
+ options[:username] = user
29
+ end
30
+ opts.on("-P", "--password HASHED", "Sets the user's password. Use a SHA1 hash or -g. Defaults to test.") do |hash|
31
+ options[:password] = hash
32
+ end
33
+ opts.on("-a", "--auth KEY", "Sets a TOTP key for use with Google Authenticator.") do |key|
34
+ options[:auth] = key
35
+ end
36
+ opts.on("-h", "--help", "Displays this help") do
37
+ puts opts
38
+ exit
39
+ end
40
+ end.parse!
41
+
42
+ TWProxy.set :bind, options[:bind] || "127.0.0.1"
43
+
44
+ TWProxy.set :port, options[:port] || 8888
45
+ TWProxy.set :enable_ssl, options[:enable_ssl] || false
46
+ TWProxy.set :url, options[:url] || "http://localhost:8080" || ENV['WIKI_URL']
47
+ TWProxy.set :username, options[:username] || ENV['WIKI_USER'] || "user"
48
+ # Default password is test
49
+ TWProxy.set :password,
50
+ options[:password] || ENV['WIKI_PASS'] || "a94a8fe5ccb19ba61c4c0873d391e987982fbbd3"
51
+ TWProxy.set :auth, options[:auth] || ENV['WIKI_AUTH']
52
+
53
+ TWProxy.run!
54
+
@@ -0,0 +1,115 @@
1
+ require "sinatra"
2
+ require "net/http"
3
+ require "uri"
4
+ require "digest/sha1"
5
+ require "rotp"
6
+
7
+ class TWProxy < Sinatra::Base
8
+ ##
9
+ # Authenticate each request before permitting it to access the wiki
10
+ #
11
+ before do
12
+ @accepted_token = Digest::SHA1.hexdigest(
13
+ "#{settings.username}#{settings.password}#{settings.auth}"
14
+ )
15
+
16
+ token = request.cookies["auth"]
17
+
18
+ if token == @accepted_token
19
+ @authenticated = true
20
+ elsif request.path != "/login"
21
+ redirect "/login"
22
+ end
23
+ end
24
+
25
+ ##
26
+ # Authentication handlers
27
+ #
28
+ get "/login" do
29
+ redirect "/" if @authenticated
30
+ haml :login
31
+ end
32
+
33
+ post "/login" do
34
+ hashed_pass = Digest::SHA1.hexdigest(params["pass"])
35
+ user = params["user"] == settings.username
36
+ pass = hashed_pass == settings.password
37
+
38
+ if settings.auth
39
+ totp = ROTP::TOTP.new(settings.auth)
40
+ auth = totp.verify(params["auth"])
41
+ else
42
+ auth = true
43
+ end
44
+
45
+ if !auth
46
+ @error = "Auth code is incorrect"
47
+ haml :login
48
+ elsif user && pass && auth
49
+ token = Digest::SHA1.hexdigest(
50
+ "#{params["user"]}#{hashed_pass}#{settings.auth}"
51
+ )
52
+ response.set_cookie("auth", value: token,
53
+ secure: settings.enable_ssl,
54
+ expires: (Date.today >> 1).to_time,
55
+ httponly: true)
56
+
57
+ redirect "/"
58
+ else
59
+ @error = "Invalid username/password"
60
+ haml :login
61
+ end
62
+ end
63
+
64
+ get "/logout" do
65
+ response.set_cookie("auth", nil)
66
+ redirect "/login"
67
+ end
68
+
69
+ ##
70
+ # Wiki proxy
71
+ #
72
+ def get_uri
73
+ capture = URI::decode(params[:captures][0])
74
+ base = settings.url
75
+ if settings.url[-1] != '/'
76
+ base += '/'
77
+ end
78
+
79
+ uri = URI.parse("#{base}#{URI::encode(capture)}")
80
+ end
81
+
82
+ put /\/(.*)/ do
83
+ uri = get_uri
84
+ http = Net::HTTP.new(uri.host, uri.port)
85
+
86
+ req = Net::HTTP::Put.new(uri.request_uri, initheader = { "Content-Type" => "application/json"})
87
+ request.body.rewind
88
+ req.body = request.body.read
89
+
90
+ resp = http.request(req)
91
+
92
+ status resp.code
93
+ headers({"Etag" => resp["Etag"]})
94
+ resp.body
95
+ end
96
+
97
+ delete /\/(.*)/ do
98
+ uri = get_uri
99
+ http = Net::HTTP.new(uri.host, uri.port)
100
+
101
+ req = Net::HTTP::Delete.new(uri.request_uri)
102
+ resp = http.request(req)
103
+
104
+ status resp.code
105
+ resp.body
106
+ end
107
+
108
+ get /\/(.*)/ do
109
+ uri = get_uri
110
+ http = Net::HTTP.new(uri.host, uri.port)
111
+ req = Net::HTTP::Get.new(uri.request_uri)
112
+
113
+ http.request(req).body
114
+ end
115
+ end
@@ -0,0 +1,56 @@
1
+ !!!
2
+ %html
3
+ %head
4
+ %title wiki - login
5
+ :css
6
+ body {
7
+ font-family: 'Helvetica';
8
+ color: #111;
9
+ font-weight: 300;
10
+ }
11
+ .box {
12
+ width: 400px;
13
+ position: fixed;
14
+ top: 50%;
15
+ left: 50%;
16
+ transform: translate(-50%, -80%);
17
+ background: #EEE;
18
+ padding: 15px;
19
+ box-sizing: border-box;
20
+ }
21
+ h1 {
22
+ margin: 0px 0px 10px 0px;
23
+ font-weight: 300;
24
+ font-size: 24px;
25
+ }
26
+ input {
27
+ display: block;
28
+ width: 100%;
29
+ margin-bottom: 5px;
30
+ font-family: 'Helvetica';
31
+ font-weight: 300;
32
+ font-size: 18px;
33
+ padding: 5px;
34
+ box-sizing: border-box;
35
+ outline: none;
36
+ }
37
+
38
+ input[type=submit] {
39
+ background: #00B4FF;
40
+ border: 0px;
41
+ color: #FFF;
42
+ margin-bottom: 0px;
43
+ }
44
+
45
+ %body
46
+ .box
47
+ %h1 Login
48
+ - if @error
49
+ %p= @error
50
+
51
+ %form{action:'/login', method: 'POST'}
52
+ %input{type: 'text', placeholder: 'Username', name: 'user'}
53
+ %input{type: 'password', placeholder: 'Password', name: 'pass'}
54
+ - if settings.auth
55
+ %input{type: 'number', placeholder: 'Auth code', name: 'auth'}
56
+ %input{type: 'submit', value: 'Login'}
data/lib/twproxy.rb ADDED
@@ -0,0 +1 @@
1
+ require 'twproxy/proxy'
data/twproxy.gemspec ADDED
@@ -0,0 +1,18 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = "twproxy"
3
+ s.version = "0.0.1"
4
+ s.date = "2016-04-09"
5
+ s.summary = "TiddlyWiki Proxy"
6
+ s.description = "An authenticated proxy for TiddlyWiki"
7
+ s.authors = ["Steve Gattuso"]
8
+ s.email = "steve@stevegattuso.me"
9
+ s.files = ["lib/twproxy.rb"]
10
+ s.files = %w(Gemfile LICENSE README.md twproxy.gemspec) + Dir['lib/**/*', 'bin/*']
11
+ s.license = "MIT"
12
+ s.executables << 'twproxy'
13
+
14
+ s.add_dependency 'sinatra', '~> 1.4'
15
+ s.add_dependency 'haml', '~> 4.0'
16
+ s.add_dependency 'rotp', '~> 2.1'
17
+ end
18
+
metadata ADDED
@@ -0,0 +1,94 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: twproxy
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Steve Gattuso
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-04-09 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: sinatra
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.4'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.4'
27
+ - !ruby/object:Gem::Dependency
28
+ name: haml
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '4.0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '4.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rotp
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '2.1'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '2.1'
55
+ description: An authenticated proxy for TiddlyWiki
56
+ email: steve@stevegattuso.me
57
+ executables:
58
+ - twproxy
59
+ extensions: []
60
+ extra_rdoc_files: []
61
+ files:
62
+ - Gemfile
63
+ - LICENSE
64
+ - README.md
65
+ - bin/twproxy
66
+ - lib/twproxy.rb
67
+ - lib/twproxy/proxy.rb
68
+ - lib/twproxy/views/login.haml
69
+ - twproxy.gemspec
70
+ homepage:
71
+ licenses:
72
+ - MIT
73
+ metadata: {}
74
+ post_install_message:
75
+ rdoc_options: []
76
+ require_paths:
77
+ - lib
78
+ required_ruby_version: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ required_rubygems_version: !ruby/object:Gem::Requirement
84
+ requirements:
85
+ - - ">="
86
+ - !ruby/object:Gem::Version
87
+ version: '0'
88
+ requirements: []
89
+ rubyforge_project:
90
+ rubygems_version: 2.6.3
91
+ signing_key:
92
+ specification_version: 4
93
+ summary: TiddlyWiki Proxy
94
+ test_files: []