wafer 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: e9cb6796089693c43f953cb1dc6eeb0fc99d48c075b8daea41b5c1250ea444ee
4
+ data.tar.gz: be04a552608b968a700d1b4cdc99a40daa007095b16b9e104401159a31a415c6
5
+ SHA512:
6
+ metadata.gz: 736936212f74796b5003380eb17534bc4f08cc0b6537c7b99eae7abee42984c57713413300d7c3cce0d2390a97ca651ec3754956a6236896f0e445bce368d7ee
7
+ data.tar.gz: e4f83ce60cb7fdaebe8f609211ea346952b7c1d097115f6daa61ea4c7f5eb4a54a0858be5111bf62c5e9e302a500cb1f926e638c2cd615fb38bd6fa8a9946317
data/.gitignore ADDED
@@ -0,0 +1,11 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+ wafer-*.gem
10
+ **/wafer-users.json
11
+ Gemfile.lock
data/.travis.yml ADDED
@@ -0,0 +1,6 @@
1
+ ---
2
+ language: ruby
3
+ cache: bundler
4
+ rvm:
5
+ - 2.7.0
6
+ before_install: gem install bundler -v 2.1.2
@@ -0,0 +1,74 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ In the interest of fostering an open and welcoming environment, we as
6
+ contributors and maintainers pledge to making participation in our project and
7
+ our community a harassment-free experience for everyone, regardless of age, body
8
+ size, disability, ethnicity, gender identity and expression, level of experience,
9
+ nationality, personal appearance, race, religion, or sexual identity and
10
+ orientation.
11
+
12
+ ## Our Standards
13
+
14
+ Examples of behavior that contributes to creating a positive environment
15
+ include:
16
+
17
+ * Using welcoming and inclusive language
18
+ * Being respectful of differing viewpoints and experiences
19
+ * Gracefully accepting constructive criticism
20
+ * Focusing on what is best for the community
21
+ * Showing empathy towards other community members
22
+
23
+ Examples of unacceptable behavior by participants include:
24
+
25
+ * The use of sexualized language or imagery and unwelcome sexual attention or
26
+ advances
27
+ * Trolling, insulting/derogatory comments, and personal or political attacks
28
+ * Public or private harassment
29
+ * Publishing others' private information, such as a physical or electronic
30
+ address, without explicit permission
31
+ * Other conduct which could reasonably be considered inappropriate in a
32
+ professional setting
33
+
34
+ ## Our Responsibilities
35
+
36
+ Project maintainers are responsible for clarifying the standards of acceptable
37
+ behavior and are expected to take appropriate and fair corrective action in
38
+ response to any instances of unacceptable behavior.
39
+
40
+ Project maintainers have the right and responsibility to remove, edit, or
41
+ reject comments, commits, code, wiki edits, issues, and other contributions
42
+ that are not aligned to this Code of Conduct, or to ban temporarily or
43
+ permanently any contributor for other behaviors that they deem inappropriate,
44
+ threatening, offensive, or harmful.
45
+
46
+ ## Scope
47
+
48
+ This Code of Conduct applies both within project spaces and in public spaces
49
+ when an individual is representing the project or its community. Examples of
50
+ representing a project or community include using an official project e-mail
51
+ address, posting via an official social media account, or acting as an appointed
52
+ representative at an online or offline event. Representation of a project may be
53
+ further defined and clarified by project maintainers.
54
+
55
+ ## Enforcement
56
+
57
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
58
+ reported by contacting the project team at the.codefolio.guy@gmail.com. All
59
+ complaints will be reviewed and investigated and will result in a response that
60
+ is deemed necessary and appropriate to the circumstances. The project team is
61
+ obligated to maintain confidentiality with regard to the reporter of an incident.
62
+ Further details of specific enforcement policies may be posted separately.
63
+
64
+ Project maintainers who do not follow or enforce the Code of Conduct in good
65
+ faith may face temporary or permanent repercussions as determined by other
66
+ members of the project's leadership.
67
+
68
+ ## Attribution
69
+
70
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71
+ available at [https://contributor-covenant.org/version/1/4][version]
72
+
73
+ [homepage]: https://contributor-covenant.org
74
+ [version]: https://contributor-covenant.org/version/1/4/
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ source "https://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in wafer.gemspec
4
+ gemspec
5
+
6
+ gem "rake", "~> 12.0"
7
+ gem "minitest", "~> 5.0"
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2021 Noah Gibbs
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,115 @@
1
+ # Wafer
2
+
3
+ If you want a simple, production-quality, fairly thin auth system for your [ChatTheatre/SkotOS game](https://github.com/ChatTheatre/SkotOS), you want [thin-auth](https://github.com/ChatTheatre/thin-auth). Thin-auth is a perfectly reasonable PHP server app. It likes being installed at a server-like path. It needs Apache and MariaDB configured in specific ways. But it does a reasonable job of a lot of things, using a fairly small amount of code. Billing? Check. Verifying for your app that billing happened? Check. Web interface for changing settings? Check. Reasonable security? Check.
4
+
5
+ **This is not that application.**
6
+
7
+ Wafer-thin-auth is an ultra-thin dev-mode-only server, designed to impersonate SkotOS's authentication system with a minimum of ceremony in a non-production-quality way.
8
+
9
+ For now you can run it in dev and it will cheerfully believe that all your users are paid up, and always right about their passwords. It is an eternal and negligent optimist. You should never, never use it production — even once we fix that problem. There are large swaths of important functionality that it doesn't even begin to attempt. Thin-auth has good password checking, backup scripts, support for staff to manage accounts and many other things that would be worse than useless for wafer-thin-auth. Want something reasonable for production? No problem - use thin-auth.
10
+
11
+ Also, its code is far smaller and its dependencies far fewer than anything that would actually work for a real production app. 'Good' negligent optimism can be had for cheap!
12
+
13
+ But wafer-thin-auth will replace thin-auth, including its web and server components, and even the userdb-authctl shim to make an outgoing connection to the DGD server. The hope is that you can run a much less elaborate setup in development, while we get rid of an un-securable development mode of the SkotOS server.
14
+
15
+ ## Installation
16
+
17
+ Add this line to your application's Gemfile:
18
+
19
+ ```ruby
20
+ gem 'wafer'
21
+ ```
22
+
23
+ And then execute:
24
+
25
+ $ bundle install
26
+
27
+ Or install it yourself as:
28
+
29
+ $ gem install wafer
30
+
31
+ ## Usage
32
+
33
+ By default Wafer will run its AuthD and CtlD on ports 2070 and 2071, equivalent to a SkotOS portbase of 2000. You can set up your SkotOS server's UserDB file for that easily:
34
+
35
+ ~~~
36
+ # root/usr/System/data/userdb
37
+ userdb-hostname 127.0.0.1
38
+ userdb-portbase 2000
39
+ ~~~
40
+
41
+ If you'd like to change Wafer's settings - what ports it opens, and where it looks for your SkotOS server (see UserDB-Authctl below,) you can pass a settings file on the command line:
42
+
43
+ ~~~
44
+ wafer -s my_settings.json
45
+ ~~~
46
+
47
+ You can also create a new settings file with defaults:
48
+
49
+ ~~~
50
+ wafer --default-settings > new_settings_file.json
51
+ ~~~
52
+
53
+ ### UserDB-Authctl
54
+
55
+ For weird historical reasons, neither DGD nor its AuthD/CtlD want to make an outgoing network connection. Userdb-authctl is a shim between them to fix that. Wafer handles it by making outgoing network connections.
56
+
57
+ If you're not already running the userdb-authctl shim server, you'll probably want Wafer to do that for you -- or you can run userdb-authctl, but then it's one more piece to keep up and running.
58
+
59
+ In production you'll need to run userdb-authctl -- SkotOS StackScripts set this up by default. That's because you should never, never run Wafer in production. It's ***only*** for development.
60
+
61
+ ## Development
62
+
63
+ Want to do development on Wafer? It can certainly use the help!
64
+
65
+ Your go-to incantation for running the command line program:
66
+
67
+ ~~~
68
+ ruby -Ilib ./exe/wafer
69
+ ~~~
70
+
71
+ This runs Wafer while telling it where to find the latest local code (which you're modifying, right?) and letting you add flags to Ruby (e.g. -w for warnings.)
72
+
73
+ ### Normal Setup
74
+
75
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
76
+
77
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
78
+
79
+ ## Contributing
80
+
81
+ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/wafer. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/[USERNAME]/wafer/blob/master/CODE_OF_CONDUCT.md).
82
+
83
+
84
+ ## Wire Protocol
85
+
86
+ Wafer exists primarily to speak SkotOS AuthD and CtlD wire protocol to an outside-world server, just as thin-auth does. You're often best advised to look at wild-captured examples of that wire protocol.
87
+
88
+ ~~~
89
+ AUTHD: sending [keycodeauth 2726 noah 1036249917]
90
+ receive_message: 2726 OK PAID 0 (developer;terms-of-service gables)
91
+ AUTHD: sending [keycodeauth 2727 noah 1036249917]
92
+ receive_message: 2727 OK PAID 0 (developer;terms-of-service gables)
93
+ AUTHD: sending [keycodeauth 2728 noah 1036249917]
94
+ receive_message: 2728 OK PAID 0 (developer;terms-of-service gables)
95
+ AUTHD: sending [md5login 2729 noah 7355169ed8cf08d5c46bf4cd8e4c02f9]
96
+ receive_message: 2729 OK 1036249917
97
+ AUTHD: sending [keycodeauth 2730 noah 1036249917]
98
+ receive_message: 2730 OK PAID 0 (developer;terms-of-service gables)
99
+ ~~~
100
+
101
+ The easiest way, as a rule, to capture correct AuthD/CtlD exchanges is to log into your SkotOS DGD server on the telnet port and execute the following code snippet:
102
+
103
+ ~~~
104
+ code "/usr/System/sys/syslogd"->set_debug_level("/usr/UserAPI/sys/authd", 2)
105
+ ~~~
106
+
107
+ This will log all AuthD exchanges, and so it's probably too verbose to be kept on consistently - right now, any web request served on port 10080 will check with AuthD, which means a lot of exchanges.
108
+
109
+ ## License
110
+
111
+ The gem is available as open source under the terms of the GNU Affero General Public License (AGPL.)
112
+
113
+ ## Code of Conduct
114
+
115
+ Everyone interacting in the Wafer project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/[USERNAME]/wafer/blob/master/CODE_OF_CONDUCT.md).
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << "test"
6
+ t.libs << "lib"
7
+ t.test_files = FileList["test/**/*_test.rb"]
8
+ end
9
+
10
+ task :default => :test
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "wafer"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
data/config.ru ADDED
@@ -0,0 +1,56 @@
1
+
2
+ require_relative "lib/wafer.rb"
3
+ require "erubis"
4
+ require "json"
5
+
6
+ repo = JSONRepo.new ENV["WAFER_USER_REPO_FILE"]
7
+
8
+ settings = {}
9
+ if ENV["WAFER_SETTINGS_FILE"] && !ENV["WAFER_SETTINGS_FILE"].empty?
10
+ settings = JSON.load File.read(ENV["WAFER_SETTINGS_FILE"])
11
+ end
12
+ wafer = Wafer::Server.new(repo: repo, settings: settings)
13
+
14
+ # This runs the CtlD and AuthD servers in a background thread.
15
+ # If the settings say so then it also connects to the DGD incoming port to
16
+ # replace userdb-authctl.
17
+ BACKGROUND_THREAD = Thread.new do
18
+ begin
19
+ puts "Entered the thread..."
20
+ wafer.event_loop
21
+ rescue Exception
22
+ puts "Got exception in thread! Dying! #{$!.message}"
23
+ puts $!.backtrace.join("\n")
24
+ raise
25
+ end
26
+ end
27
+
28
+ RACK_404 = [404, {}, ["File not found!"]]
29
+ RET_VARS = {}
30
+
31
+ run (proc do |env|
32
+ path = env['PATH_INFO']
33
+ path = "/index.html" if path == "" || path == "/"
34
+
35
+ template_path = File.join(__dir__, "www", path) + ".erb"
36
+ if File.exist?(template_path)
37
+ erb_text = File.read(template_path)
38
+ eruby = Erubis::Eruby.new(erb_text)
39
+ RET_VARS.clear
40
+ html_text = eruby.result env: env, vars: RET_VARS,
41
+ users: repo.user_names,
42
+ play_port: wafer.settings["dgd"]["portbase"] + 80
43
+
44
+ # Should be possible to set the user and password cookies from Erb.
45
+ resp = Rack::Response.new html_text, 200, { 'Content' => 'text/html' }
46
+ if RET_VARS["user"]
47
+ resp.set_cookie "user", { value: RET_VARS["user"], path: "/", expires: Time.now+30*24*60*60 }
48
+ # "pass" is used for the keycode, not an actual password
49
+ resp.set_cookie "pass", { value: "17", path: "/", expires: Time.now+30*24*60*60 }
50
+ end
51
+ resp.finish
52
+ else
53
+ [404, {}, [ "File not found: #{template_path}" ]]
54
+ end
55
+
56
+ end)
data/exe/wafer ADDED
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env ruby -w
2
+
3
+ require "wafer"
4
+ require "wafer/version"
5
+
6
+ require "json"
7
+
8
+ require "optimist"
9
+
10
+ OPTS = Optimist::options do
11
+ version "Wafer version #{Wafer::VERSION}"
12
+ banner <<BANNER
13
+ Wafer runs SkotOS authentication servers for developing your ChatTheatre/SkotOS app.
14
+
15
+ Available options:
16
+ BANNER
17
+ opt :default_settings, "Dump default settings as JSON to console and exit"
18
+ opt :settings_file, "Reading settings from JSON settings file", type: String, short: "-s"
19
+ opt :repo_file, "JSON file containing user info", :default => "wafer-users.json"
20
+ end
21
+
22
+ if OPTS[:default_settings]
23
+ puts JSON.pretty_generate(Wafer::DEFAULT_SETTINGS)
24
+ exit
25
+ end
26
+
27
+ puts "Try to instantiate Wafer server - verifying that settings are legal..."
28
+ repo = JSONRepo.new OPTS[:repo_file]
29
+ settings = {}
30
+ settings = JSON.load File.read(OPTS[:settings_file]) if OPTS[:settings_file]
31
+ wafer_server = Wafer::Server.new(repo: repo, settings: settings)
32
+ puts "No problems found."
33
+
34
+ # This won't return. It will run the server until interrupted.
35
+ system({
36
+ "WAFER_USER_REPO_FILE" => OPTS[:repo_file],
37
+ "WAFER_SETTINGS_FILE" => OPTS[:settings_file],
38
+ }, "rackup", "-p", wafer_server.settings["http"]["port"].to_s)
data/lib/wafer.rb ADDED
@@ -0,0 +1,149 @@
1
+ #require "wafer/version"
2
+
3
+ require 'socket'
4
+ require 'cgi'
5
+
6
+ module Wafer
7
+ class Error < StandardError; end
8
+
9
+ DEFAULT_SETTINGS = {
10
+ "http" => {
11
+ "port" => 2072,
12
+ }.freeze,
13
+ "authServer" => {
14
+ "selectTimeout" => 5.0,
15
+ }.freeze,
16
+ "dgd" => {
17
+ "portbase" => 10_000,
18
+ "serverIP" => "127.0.0.1",
19
+ }.freeze,
20
+ }.freeze
21
+
22
+ class Server
23
+ attr_reader :repo
24
+ attr_reader :settings
25
+
26
+ def initialize(repo:, settings: {})
27
+ @repo = repo
28
+
29
+ # Make a copy of default settings to allow modification
30
+ @settings = copy_of_default_settings
31
+ @settings.merge! settings
32
+
33
+ @read_sockets = []
34
+ @err_sockets = []
35
+ @socket_types = {}
36
+ @seq_numbers = {}
37
+ @socket_buffer = {}
38
+ end
39
+
40
+ def copy_of_default_settings
41
+ Hash[DEFAULT_SETTINGS.map { |key, value| [key, value.dup] }]
42
+ end
43
+
44
+ def log(message)
45
+ pre = "[#{Time.now}] "
46
+ puts pre + message
47
+ end
48
+
49
+ def conn_connect(conn_type)
50
+ port = @settings["dgd"]["portbase"] + (conn_type == :auth ? 70 : 71)
51
+ sock = TCPSocket.open @settings["dgd"]["serverIP"], port
52
+ @socket_types[sock] = conn_type
53
+ @read_sockets.push sock
54
+ @err_sockets.push sock
55
+ @socket_buffer[sock] = ""
56
+
57
+ return sock
58
+ end
59
+
60
+ def conn_reconnect(conn)
61
+ @read_sockets -= [ conn ]
62
+ @err_sockets -= [ conn ]
63
+ socket_type = @socket_types[conn]
64
+ @socket_types.delete(conn)
65
+ @seq_numbers.delete(conn)
66
+ @socket_buffer.delete(conn)
67
+ begin
68
+ log("Closing connection of type #{socket_type.inspect}...")
69
+ conn.close
70
+ rescue
71
+ STDERR.puts $!.inspect
72
+ log("Closing connection of type #{socket_type.inspect}... (But got an error, failing - this is common.)")
73
+ end
74
+
75
+ STDERR.puts "Reconnecting outgoing connection of type #{socket_type.inspect}..."
76
+ conn_connect(socket_type)
77
+ end
78
+
79
+ def send_error(conn, message)
80
+ seq = @seq_numbers[conn]
81
+ log("Error on conn (#{seq}): #{message}")
82
+ conn.write "#{seq} ERR #{message}\n"
83
+ end
84
+
85
+ def send_ok(conn, message)
86
+ ok_message = "#{@seq_numbers[conn]} OK #{message}\n"
87
+ STDERR.puts "Sending OK message: #{ok_message.inspect}"
88
+ conn.write ok_message
89
+ end
90
+
91
+ def event_loop
92
+ puts "Settings:"
93
+ puts JSON.pretty_generate(@settings)
94
+
95
+ conn_connect(:auth)
96
+ conn_connect(:ctl)
97
+
98
+ loop do
99
+ sleep 0.1
100
+ readable, _, errorable, = IO.select @read_sockets, [], @err_sockets, @settings["authServer"]["selectTimeout"]
101
+
102
+ readable ||= []
103
+ errorable ||= []
104
+
105
+ puts "Selected... R: #{readable.size} / #{@read_sockets.size} E: #{errorable.size} / #{@err_sockets.size}"
106
+
107
+ # Close connections on error
108
+ errorable.each { |errant_conn| conn_reconnect(errant_conn) }
109
+
110
+ (readable - errorable).each do |conn|
111
+ STDERR.puts "Preparing for read..."
112
+ data = conn.recv_nonblock(2048)
113
+ if !data || data == ""
114
+ STDERR.puts "No data - need to reconnect?"
115
+ sleep 0.5
116
+ #conn_reconnect(conn)
117
+ next
118
+ end
119
+ STDERR.puts "Successful read: #{data.inspect}"
120
+ @socket_buffer[conn] += data
121
+
122
+ while @socket_buffer[conn]["\r\n"]
123
+ line, remainder = @socket_buffer[conn].split("\r\n", 2)
124
+ @socket_buffer[conn] = remainder
125
+
126
+ parts = line.chomp.strip.split(" ").map { |part| CGI::unescape(part) }
127
+ next if parts == [] # No-op
128
+
129
+ STDERR.puts "Successful parse: #{parts.inspect}"
130
+
131
+ case @socket_types[conn]
132
+ when :ctl
133
+ ctl_respond(conn, parts)
134
+ when :auth
135
+ auth_respond(conn, parts)
136
+ else
137
+ log("Wrong socket type #{@socket_types[conn].inspect} for connection!")
138
+ conn_disconnect(conn)
139
+ end
140
+ end
141
+ end
142
+ end
143
+ end
144
+ end
145
+ end
146
+
147
+ require_relative "wafer/json_repo"
148
+ require_relative "wafer/auth_server"
149
+ require_relative "wafer/ctl_server"
@@ -0,0 +1,173 @@
1
+ class Wafer::Server
2
+ NO_LOG_CMDS = ["passlogin", "passwordauth"]
3
+ NO_USER_VERIFY_CMDS = ["emailused", "emaillookup"]
4
+ KEYCODE_VERIFY_CMDS = ["checkaccess", "convertaccount", "getping", "getprop", "pinguser"]
5
+ HASH_VERIFY_CMDS = ["md5login", "md5auth"]
6
+ PASSWORD_VERIFY_CMDS = ["passwordlogin", "passwordauth"]
7
+
8
+ def auth_respond(conn, first_message)
9
+ if first_message[0][0] == ":"
10
+ secure_auth = true
11
+ user_name = first_message[0][1..-1]
12
+ code = first_message[1]
13
+ command = first_message[2]
14
+ @seq_numbers[conn] = first_message[3]
15
+ message = first_message[4..-1]
16
+ else
17
+ secure_auth = false
18
+ command = first_message[0]
19
+ @seq_numbers[conn] = first_message[1]
20
+ user_name = first_message[2]
21
+ code = false
22
+ message = first_message[3..-1]
23
+ end
24
+
25
+ if command == "" || user_name == ""
26
+ send_error(conn, "BAD INPUT")
27
+ end
28
+
29
+ uid = @repo.uid_by_name(user_name)
30
+
31
+ if NO_LOG_CMDS.include?(command)
32
+ # Don't log passwords
33
+ log("Recorded command #{command.inspect} for user #{user_name.inspect}")
34
+ else
35
+ log("Auth server: #{first_message.inspect}")
36
+ end
37
+
38
+ # All commands but two require verifying the user isn't deleted, banned, etc.
39
+ unless NO_USER_VERIFY_CMDS.include?(command)
40
+ allowed, err = @repo.is_user_ok(uid)
41
+ return send_error(conn, err) unless allowed
42
+ end
43
+
44
+ # Some commands need the keycode to be valid if supplied
45
+ if KEYCODE_VERIFY_CMDS.include?(command) && code
46
+ allowed, err = @repo.is_keycode_ok(uid, code)
47
+ return send_error(conn, err) unless allowed
48
+ end
49
+
50
+ if HASH_VERIFY_CMDS.include?(command)
51
+ allowed, err = @repo.is_hash_ok(uid, message[0])
52
+ return send_error(conn, err) unless allowed
53
+ end
54
+
55
+ if PASSWORD_VERIFY_CMDS.include?(command)
56
+ allowed, err = @repo.is_password_ok(uid, message[0])
57
+ return send_error(conn, err) unless allowed
58
+ end
59
+
60
+ case command
61
+
62
+ when "checkaccess"
63
+ if @repo.user_has_access?(uid, message[0])
64
+ return send_ok(conn, "ACCESS")
65
+ else
66
+ return send_error(conn, "NOAUTH")
67
+ end
68
+
69
+ when "convertaccount"
70
+ if message[0] == "premium"
71
+ @repo.user_set_flag(uid, "premium")
72
+ return send_ok(conn, "premium")
73
+ elsif message[0] == "basic"
74
+ @repo.user_unset_flag(uid, "premium")
75
+ return send_ok(conn, "basic")
76
+ else
77
+ return send_error(conn, "Unknown conversion (#{message[0]})")
78
+ end
79
+
80
+ when "emaillookup"
81
+ user = @repo.user_by_field("email", user_name)
82
+ if user
83
+ return send_ok(conn, user["name"])
84
+ else
85
+ return send_error(conn, "no such email")
86
+ end
87
+
88
+ when "emailused"
89
+ user = @repo.user_by_field("email", user_name)
90
+ if user
91
+ return send_ok(conn, "YES")
92
+ else
93
+ return send_error(conn, "no such email")
94
+ end
95
+
96
+ when "getping"
97
+ # We don't do real email pings with this server
98
+ user = @repo.user_by_id(uid)
99
+ if user
100
+ return send_ok(conn, "#{uid} #{user["email"]} 17171717171717171717")
101
+ else
102
+ return send_error(conn, "NO PING")
103
+ end
104
+
105
+ when "getprop"
106
+ if secure_auth
107
+ prop = message[1]
108
+ else
109
+ prop = message[0]
110
+ end
111
+
112
+ user = @repo.user_by_id(uid)
113
+ return send_ok(conn, user[prop])
114
+
115
+ when "keycodeauth"
116
+ code = message[0] unless code
117
+
118
+ allowed, err = @repo.is_keycode_ok(uid, code)
119
+ STDERR.puts "keycodeauth(#{uid.inspect}, #{code.inspect}) = [#{allowed.inspect}, #{err.inspect}]"
120
+ return send_error(conn, err) unless allowed
121
+
122
+ return send_error(conn, "TOS") unless @repo.user_has_tos?(uid)
123
+ return send_error(conn, "USER HAS NO EMAIL") unless @repo.user_has_verified_email?(uid)
124
+
125
+ return send_auth_status(conn, uid)
126
+
127
+ when "md5login"
128
+ return send_ok(conn, @repo.user_keycode(uid))
129
+
130
+ when "md5auth"
131
+ return send_auth_status(conn, uid)
132
+
133
+ when "passwordlogin"
134
+ return send_ok(conn, @repo.user_keycode(uid))
135
+
136
+ when "passwordauth"
137
+ return send_auth_status(conn, uid)
138
+
139
+ when "pinguser"
140
+ return send_ok(conn, "OK")
141
+
142
+ when "setemail"
143
+ @repo.user_set_email(uid, message[0])
144
+ return send_ok(conn, "YES")
145
+
146
+ when "tempkeycode"
147
+ return send_ok(conn, @repo.user_keycode(uid))
148
+
149
+ when "tempguarantee"
150
+ return send_error(conn, "DOES NOT SUPPORT")
151
+ end
152
+
153
+ return send_error(conn, "BAD COMMAND(#{command.inspect})")
154
+ end
155
+
156
+ def send_auth_status(conn, uid)
157
+ user_type = @repo.user_account_type(uid)
158
+ user_status = @repo.user_account_status(uid)
159
+ user_string = "(#{user_type};#{user_status})"
160
+
161
+ if @repo.user_is_paid?(uid)
162
+ if user_type == "trial"
163
+ return send_ok(conn, "TRIAL #{@repo.user_next_stamp(uid)} #{user_string}")
164
+ elsif ["developer", "staff", "free"].include?(user_type)
165
+ return send_ok(conn, "PAID 0 #{user_string}")
166
+ else
167
+ return send_ok(conn, "PAID #{@repo.user_next_stamp(uid)} #{user_string}")
168
+ end
169
+ else
170
+ return send_ok(conn, "UNPAID #{user_string}")
171
+ end
172
+ end
173
+ end
@@ -0,0 +1,14 @@
1
+ class Wafer::Server
2
+ def ctl_respond(conn, parts)
3
+ return send_error(conn, "BAD INPUT") if parts.size < 3 || parts.size > 9
4
+
5
+ @seq_numbers[conn] = parts[1]
6
+ command = parts[0]
7
+
8
+ if command == "announce"
9
+ return send_ok(conn, "OK")
10
+ end
11
+
12
+ return send_error(conn, "UNIMPLEMENTED")
13
+ end
14
+ end
@@ -0,0 +1,210 @@
1
+ require 'json'
2
+ require 'digest'
3
+ require 'digest/md5'
4
+
5
+ class JSONRepo
6
+ EMPTY_REPO = {
7
+ "users" => [
8
+ {
9
+ "id" => 1,
10
+ "name" => "admin",
11
+ "email" => "admin@example.com",
12
+ "creation_time" => Time.now,
13
+ "password" => "ABCDEFG",
14
+ "pay_day" => 0,
15
+ "next_month" => 0,
16
+ "next_year" => 0,
17
+ "next_stamp" => 0,
18
+ "account_credit" => 0.0,
19
+ "account_type" => "staff", # regular, trial, free, developer, staff
20
+
21
+ "flags" => ["terms-of-service"], # no-email, premium, deleted, terms-of-service, banned
22
+
23
+ "access" => [ "gables" ], # What game names the user has access to
24
+
25
+ "keycode" => {
26
+ "keycode" => "17",
27
+ "keycode_stamp" => Time.now,
28
+ },
29
+ },
30
+ {
31
+ "id" => 1,
32
+ "name" => "bobo",
33
+ "email" => "bobo@example.com",
34
+ "creation_time" => Time.now,
35
+ "password" => "ABCDEFG",
36
+ "pay_day" => 0,
37
+ "next_month" => 0,
38
+ "next_year" => 0,
39
+ "next_stamp" => 0,
40
+ "account_credit" => 0.0,
41
+ "account_type" => "staff", # regular, trial, free, developer, staff
42
+
43
+ "flags" => ["terms-of-service"], # no-email, premium, deleted, terms-of-service, banned
44
+
45
+ "access" => [ "gables" ], # What game names the user has access to
46
+
47
+ "keycode" => {
48
+ "keycode" => "17",
49
+ "keycode_stamp" => Time.now,
50
+ },
51
+ },
52
+ ],
53
+ }
54
+
55
+ def initialize(json_filename)
56
+ unless File.exist?(json_filename)
57
+ File.open(json_filename, "w") { |f| f.print(JSON.pretty_generate EMPTY_REPO) }
58
+ end
59
+ @contents = JSON.load(File.read json_filename)
60
+ end
61
+
62
+ def user_names
63
+ @contents["users"].map { |u| u["name"] }
64
+ end
65
+
66
+ def uid_by_name(name)
67
+ user = user_by_name(name)
68
+ user && user["id"]
69
+ end
70
+
71
+ def user_by_name(name)
72
+ user_by_field("name", name)
73
+ end
74
+
75
+ def user_by_id(id)
76
+ user_by_field("id", id)
77
+ end
78
+
79
+ def user_by_field(field, value)
80
+ @contents["users"].detect { |u| u[field] == value }
81
+ end
82
+
83
+ def user_has_access?(uid, game)
84
+ user = user_by_id(uid)
85
+ user && user["access"].include?(game)
86
+ end
87
+
88
+ def user_has_tos?(uid)
89
+ user = user_by_id(uid)
90
+ user && user["flags"].include?("terms-of-service")
91
+ end
92
+
93
+ # We don't do email pings
94
+ def user_has_verified_email?(uid)
95
+ user = user_by_id(uid)
96
+ !!user
97
+ end
98
+
99
+ def user_set_email(uid, email)
100
+ user = user_by_id(uid)
101
+ user && user["email"] = email
102
+ end
103
+
104
+ def user_account_type(uid)
105
+ user = user_by_id(uid)
106
+ user ? user["account_type"] : nil
107
+ end
108
+
109
+ # Note: we ignore "freebie" users. We don't have that.
110
+ def user_account_status(uid)
111
+ user = user_by_id(uid)
112
+ return nil unless user
113
+
114
+ (user["flags"] + user["access"]).join(" ")
115
+ end
116
+
117
+ def user_set_flag(uid, flag)
118
+ user = user_by_id(uid)
119
+ user && user["flags"] |= [flag]
120
+ end
121
+
122
+ def user_unset_flag(uid, flag)
123
+ user = user_by_id(uid)
124
+ user && user["flags"] -= [flag]
125
+ end
126
+
127
+ private
128
+
129
+ def new_random_keycode
130
+ rand(1 << 27)
131
+ end
132
+
133
+ public
134
+
135
+ def user_keycode(uid)
136
+ user = user_by_id(uid)
137
+ user && new_random_keycode
138
+ end
139
+
140
+ def user_set_keycode(uid)
141
+ user = user_by_id(uid)
142
+ raise "No such user!" unless user
143
+ user["keycode"] = { "keycode" => new_random_keycode, "stamp" => Time.now }
144
+ end
145
+
146
+ # Miraculously, every user's next stamp is awhile in the future.
147
+ def user_next_stamp(uid)
148
+ Time.now.to_i + 3600 * 24 * 20
149
+ end
150
+
151
+ def user_is_paid?(uid)
152
+ user = user_by_id(uid)
153
+ user && true
154
+ end
155
+
156
+ def is_user_ok(uid)
157
+ user = user_by_id(uid)
158
+
159
+ if !user
160
+ return [false, "NO SUCH USER"]
161
+ elsif user["flags"].include?("deleted")
162
+ return [false, "NO SUCH USER"]
163
+ elsif user["flags"].include?("banned")
164
+ return [false, "ACCOUNT BLOCKED"]
165
+ end
166
+
167
+ return [true, ""]
168
+ end
169
+
170
+ def is_keycode_ok(uid, code)
171
+ return [false, "BAD KEYCODE"] unless code
172
+
173
+ # Keycode handling here is insultingly trivial and intentionally insecure.
174
+
175
+ return [true, ""]
176
+ end
177
+
178
+ # In thin-auth, this uses PHP's built-in password hashing.
179
+ def is_password_ok(uid, pass)
180
+ user = user_by_id(uid)
181
+
182
+ return [false, "NO SUCH USER"] unless user
183
+
184
+ if user["password"] == pass
185
+ [true, ""]
186
+ else
187
+ [false, "BAD PASSWORD"]
188
+ end
189
+ end
190
+
191
+ def is_hash_ok(uid, hash)
192
+ user = user_by_id(uid)
193
+ return [false, "NO SUCH USER"] unless user
194
+
195
+ STDERR.puts "Checking md5 hash: #{hash.inspect}"
196
+
197
+ keycode = user["keycode"]["keycode"]
198
+ real_hash = Digest::MD5.hexdigest(user["name"] + keycode + "NONE")
199
+
200
+ STDERR.puts " Real hash: #{real_hash.inspect}"
201
+
202
+ # Skip the check and just always say it's fine.
203
+ #if real_hash == hash
204
+ return [true, ""]
205
+ #else
206
+ # return [false, "BAD HASH"]
207
+ #end
208
+ end
209
+
210
+ end
@@ -0,0 +1,3 @@
1
+ module Wafer
2
+ VERSION = "0.2.0"
3
+ end
data/wafer.gemspec ADDED
@@ -0,0 +1,32 @@
1
+ require_relative 'lib/wafer/version'
2
+
3
+ Gem::Specification.new do |spec|
4
+ spec.name = "wafer"
5
+ spec.version = Wafer::VERSION
6
+ spec.authors = ["Noah Gibbs"]
7
+ spec.email = ["the.codefolio.guy@gmail.com"]
8
+
9
+ spec.summary = %q{Wafer is a development-only auth server for SkotOS games.}
10
+ spec.description = %q{Wafer is a development-only AuthD/CtlD and web server for developing your SkotOS-based games. For a production-quality auth server, please use thin-auth instead.}
11
+ spec.homepage = "http://github.com/noahgibbs/wafer"
12
+ spec.license = "AGPL"
13
+ spec.required_ruby_version = Gem::Requirement.new(">= 2.3.0")
14
+
15
+ spec.metadata["allowed_push_host"] = "https://rubygems.org"
16
+
17
+ spec.metadata["homepage_uri"] = spec.homepage
18
+
19
+ # Specify which files should be added to the gem when it is released.
20
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
21
+ spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
22
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
23
+ end
24
+ spec.bindir = "exe"
25
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
26
+ spec.require_paths = ["lib"]
27
+
28
+ spec.add_runtime_dependency "optimist", "~>3.0.1"
29
+ spec.add_runtime_dependency "erubis"
30
+ spec.add_runtime_dependency "rack"
31
+ spec.add_runtime_dependency "json"
32
+ end
@@ -0,0 +1,47 @@
1
+ <html>
2
+ <head>
3
+ <script>
4
+
5
+ //document.querySelector(".button").addEventListener("click", (e) => { /* ... */ });
6
+
7
+ // Define a convenience method and use it
8
+ var ready = (callback) => {
9
+ if (document.readyState != "loading") callback();
10
+ else document.addEventListener("DOMContentLoaded", callback);
11
+ }
12
+
13
+ ready(() => {
14
+ /* After DOM has fully loaded... */
15
+ document.querySelector("#user-select").addEventListener("change", (e) => {
16
+ let user = document.querySelector("#user-select").value;
17
+ let playUrl = "http://localhost:<%= play_port %>/gables/gables.htm?charName=" + user;
18
+ document.querySelector("#play-url").href = playUrl;
19
+ document.cookie = "user=" + user + ";path=/";
20
+ console.log("Setting user to be " + user + ".");
21
+ console.log("Setting Play URL to be " + playUrl + ".");
22
+ });
23
+ });
24
+ </script>
25
+ </head>
26
+ <body>
27
+
28
+ <%
29
+ first_user = (users - ["admin"]).first # Set cookie for first non-admin user
30
+ vars["user"] = first_user
31
+ %>
32
+
33
+ <h1>Wafer DGD Login</h1>
34
+
35
+ <ul>
36
+ <li><a id="play-url" href="http://localhost:<%= play_port %>/gables/gables.htm?charName=<%= first_user %>">Play</a></li>
37
+ <li><a href="http://localhost:<%= play_port %>/gables/TreeOfWOE.html">Tree of WOE</a></li>
38
+ </ul>
39
+
40
+ <select id="user-select">
41
+ <% users.each do |user| %>
42
+ <option value="<%= user %>"><%= user %></option>
43
+ <% end %>
44
+ </select>
45
+
46
+ </body>
47
+ </html>
metadata ADDED
@@ -0,0 +1,122 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: wafer
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.0
5
+ platform: ruby
6
+ authors:
7
+ - Noah Gibbs
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2021-03-24 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: optimist
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 3.0.1
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 3.0.1
27
+ - !ruby/object:Gem::Dependency
28
+ name: erubis
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rack
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: json
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ description: Wafer is a development-only AuthD/CtlD and web server for developing
70
+ your SkotOS-based games. For a production-quality auth server, please use thin-auth
71
+ instead.
72
+ email:
73
+ - the.codefolio.guy@gmail.com
74
+ executables:
75
+ - wafer
76
+ extensions: []
77
+ extra_rdoc_files: []
78
+ files:
79
+ - ".gitignore"
80
+ - ".travis.yml"
81
+ - CODE_OF_CONDUCT.md
82
+ - Gemfile
83
+ - LICENSE.txt
84
+ - README.md
85
+ - Rakefile
86
+ - bin/console
87
+ - bin/setup
88
+ - config.ru
89
+ - exe/wafer
90
+ - lib/wafer.rb
91
+ - lib/wafer/auth_server.rb
92
+ - lib/wafer/ctl_server.rb
93
+ - lib/wafer/json_repo.rb
94
+ - lib/wafer/version.rb
95
+ - wafer.gemspec
96
+ - www/index.html.erb
97
+ homepage: http://github.com/noahgibbs/wafer
98
+ licenses:
99
+ - AGPL
100
+ metadata:
101
+ allowed_push_host: https://rubygems.org
102
+ homepage_uri: http://github.com/noahgibbs/wafer
103
+ post_install_message:
104
+ rdoc_options: []
105
+ require_paths:
106
+ - lib
107
+ required_ruby_version: !ruby/object:Gem::Requirement
108
+ requirements:
109
+ - - ">="
110
+ - !ruby/object:Gem::Version
111
+ version: 2.3.0
112
+ required_rubygems_version: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - ">="
115
+ - !ruby/object:Gem::Version
116
+ version: '0'
117
+ requirements: []
118
+ rubygems_version: 3.1.2
119
+ signing_key:
120
+ specification_version: 4
121
+ summary: Wafer is a development-only auth server for SkotOS games.
122
+ test_files: []