icfs 0.1.3 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/bin/icfs_demo_fcgi.rb +2 -0
- data/{bin/icfs_demo_ssl_gen.rb → devel/demo/ssl_gen.rb} +25 -13
- data/devel/demo/ssl_gen.yml +14 -0
- data/devel/icfs-wrk/Dockerfile +1 -1
- data/devel/run/base.rb +92 -0
- data/devel/run/copy-s3.rb +2 -0
- data/devel/run/email.rb +36 -0
- data/devel/run/email_imap.rb +43 -0
- data/devel/run/email_smime.rb +47 -0
- data/devel/run/init-icfs.rb +2 -0
- data/devel/run/webrick.rb +5 -57
- data/lib/icfs/api.rb +101 -90
- data/lib/icfs/cache.rb +2 -0
- data/lib/icfs/cache_elastic.rb +127 -125
- data/lib/icfs/{web/config.rb → config.rb} +3 -3
- data/lib/icfs/{web/config_redis.rb → config_redis.rb} +8 -8
- data/lib/icfs/{web/config_s3.rb → config_s3.rb} +8 -8
- data/lib/icfs/demo/auth.rb +5 -7
- data/lib/icfs/demo/static.rb +2 -0
- data/lib/icfs/elastic.rb +10 -8
- data/lib/icfs/email/basic.rb +242 -0
- data/lib/icfs/email/core.rb +293 -0
- data/lib/icfs/email/from.rb +52 -0
- data/lib/icfs/email/imap.rb +148 -0
- data/lib/icfs/email/smime.rb +139 -0
- data/lib/icfs/items.rb +5 -3
- data/lib/icfs/store.rb +20 -18
- data/lib/icfs/store_fs.rb +7 -5
- data/lib/icfs/store_s3.rb +4 -2
- data/lib/icfs/users.rb +5 -3
- data/lib/icfs/users_fs.rb +8 -6
- data/lib/icfs/users_redis.rb +12 -10
- data/lib/icfs/users_s3.rb +6 -4
- data/lib/icfs/utils/backup.rb +30 -29
- data/lib/icfs/utils/check.rb +36 -34
- data/lib/icfs/validate.rb +24 -15
- data/lib/icfs/web/auth_ssl.rb +7 -9
- data/lib/icfs/web/client.rb +671 -679
- data/lib/icfs.rb +174 -10
- metadata +16 -7
- data/devel/devel-webrick.yml +0 -49
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 28031e9c5c67e30c2cf4778fbf56a7840117d256e4384e6e044e7cc888958d09
|
4
|
+
data.tar.gz: 3abea632e084b00bd09ac570710eab52e99a152d073c6f96779a04c23dbc1c21
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cd28dc30f59e5bec1b272508d5b64ecb950a3e2b6d26d4318156069d19c338093177302ac09207a4b76b7decfa8272f84a8839f3165eeee28fdb9f15ae5c6ef3
|
7
|
+
data.tar.gz: '08f19a62a3d3c21cdf46c8120bd0b5846107a57409aba71939587affed4e7daf32c4d0bd805919d884baa4d24b520521527f62e8edf66ed6f886cf2237122b27'
|
data/bin/icfs_demo_fcgi.rb
CHANGED
@@ -10,15 +10,23 @@
|
|
10
10
|
# This program is distributed WITHOUT ANY WARRANTY; without even the
|
11
11
|
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
12
12
|
|
13
|
+
# frozen_string_literal: true
|
14
|
+
|
13
15
|
require 'openssl'
|
16
|
+
require 'yaml'
|
17
|
+
|
18
|
+
# read the configuration
|
19
|
+
cfg = YAML.load(File.read(ARGV[0]))
|
20
|
+
|
14
21
|
|
22
|
+
serial = Time.now.to_i
|
15
23
|
|
16
|
-
# make a CA
|
24
|
+
# make a CA cert
|
17
25
|
ca_key = OpenSSL::PKey::RSA.new 2048
|
18
26
|
ca_cert = OpenSSL::X509::Certificate.new
|
19
27
|
ca_cert.version = 2
|
20
|
-
ca_cert.serial =
|
21
|
-
ca_cert.subject = OpenSSL::X509::Name.parse('
|
28
|
+
ca_cert.serial = serial
|
29
|
+
ca_cert.subject = OpenSSL::X509::Name.parse(cfg['ca']['cn'])
|
22
30
|
ca_cert.issuer = ca_cert.subject
|
23
31
|
ca_cert.public_key = ca_key.public_key
|
24
32
|
ca_cert.not_before = Time.now
|
@@ -36,11 +44,11 @@ ca_cert.sign(ca_key, OpenSSL::Digest::SHA256.new)
|
|
36
44
|
File.open("ca_cert.pem", "wb"){|fi| fi.write ca_cert.to_pem }
|
37
45
|
|
38
46
|
|
39
|
-
# make a server key
|
47
|
+
# make a server cert & key
|
40
48
|
srv_key = OpenSSL::PKey::RSA.new 2048
|
41
49
|
srv_cert = OpenSSL::X509::Certificate.new
|
42
50
|
srv_cert.version = 2
|
43
|
-
srv_cert.serial =
|
51
|
+
srv_cert.serial = serial + 1
|
44
52
|
srv_cert.subject = OpenSSL::X509::Name.parse('/OU=org/OU=example/OU=Test Server/CN=localhost')
|
45
53
|
srv_cert.issuer = ca_cert.subject
|
46
54
|
srv_cert.public_key = srv_key.public_key
|
@@ -52,19 +60,22 @@ ef.issuer_certificate = ca_cert
|
|
52
60
|
srv_cert.add_extension(ef.create_extension("basicConstraints", "CA:FALSE"))
|
53
61
|
srv_cert.add_extension(ef.create_extension("keyUsage", "keyEncipherment,dataEncipherment,digitalSignature"))
|
54
62
|
srv_cert.add_extension(ef.create_extension("subjectKeyIdentifier","hash",false))
|
63
|
+
srv_cert.add_extension(ef.create_extension("subjectAltName", "email:%s" % cfg['server']['email'], false))
|
55
64
|
srv_cert.sign(ca_key, OpenSSL::Digest::SHA256.new)
|
56
65
|
|
57
|
-
# save server key
|
66
|
+
# save server cert & key
|
58
67
|
File.open("srv_cert.pem", "wb"){|fi| fi.write srv_cert.to_pem }
|
59
68
|
File.open("srv_key.pem", "wb"){|fi| fi.write srv_key.to_pem }
|
60
69
|
|
61
|
-
# make client certs
|
62
|
-
|
70
|
+
# make client certs with key included
|
71
|
+
cnt = 0
|
72
|
+
cfg['clients'].each do |cc|
|
73
|
+
cnt += 1
|
63
74
|
clt_key = OpenSSL::PKey::RSA.new 2048
|
64
75
|
clt_cert = OpenSSL::X509::Certificate.new
|
65
76
|
clt_cert.version = 2
|
66
|
-
clt_cert.serial =
|
67
|
-
clt_cert.subject = OpenSSL::X509::Name.parse('
|
77
|
+
clt_cert.serial = serial + 2 + cnt
|
78
|
+
clt_cert.subject = OpenSSL::X509::Name.parse(cc['cn'])
|
68
79
|
clt_cert.issuer = ca_cert.subject
|
69
80
|
clt_cert.public_key = clt_key.public_key
|
70
81
|
clt_cert.not_before = Time.now
|
@@ -74,11 +85,12 @@ File.open("srv_key.pem", "wb"){|fi| fi.write srv_key.to_pem }
|
|
74
85
|
ef.issuer_certificate = ca_cert
|
75
86
|
clt_cert.add_extension(ef.create_extension("basicConstraints", "CA:FALSE"))
|
76
87
|
clt_cert.add_extension(ef.create_extension("keyUsage", "keyEncipherment,dataEncipherment,digitalSignature"))
|
88
|
+
clt_cert.add_extension(ef.create_extension("subjectAltName", "email:%s" % cc['email'], false))
|
77
89
|
clt_cert.sign(ca_key, OpenSSL::Digest::SHA256.new)
|
78
90
|
|
79
91
|
# pkcs12
|
80
|
-
clt_pkcs12 = OpenSSL::PKCS12.create('demo', 'client-%d' %
|
92
|
+
clt_pkcs12 = OpenSSL::PKCS12.create('demo', 'client-%d' % cnt, clt_key, clt_cert)
|
81
93
|
|
82
|
-
# save cert
|
83
|
-
File.open('clt_%d.pfx' %
|
94
|
+
# save cert w/key
|
95
|
+
File.open('clt_%d.pfx' % cnt, 'wb'){|fi| fi.write clt_pkcs12.to_der }
|
84
96
|
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
|
2
|
+
ca:
|
3
|
+
cn: "/OU=org/OU=example/CN=Test Root CA"
|
4
|
+
|
5
|
+
server:
|
6
|
+
cn: "/OU=org/OU=example/OU=Test Server/CN=localhost"
|
7
|
+
email: "server@example.org"
|
8
|
+
|
9
|
+
clients:
|
10
|
+
- cn: "/OU=org/OU=example/OU=Test Client/CN=client 1"
|
11
|
+
email: "client1@example.org"
|
12
|
+
|
13
|
+
- cn: "/OU=org/OU=example/OU=Test Client/CN=client 2"
|
14
|
+
email: "client2example.org"
|
data/devel/icfs-wrk/Dockerfile
CHANGED
@@ -16,7 +16,7 @@ RUN apk update && \
|
|
16
16
|
apk upgrade && \
|
17
17
|
apk --update add ruby fcgi ruby-json tzdata vim curl git bash && \
|
18
18
|
apk --update add --virtual build-deps ruby-dev build-base fcgi-dev && \
|
19
|
-
gem install -N rack webrick etc faraday yard aws-sdk-s3 redis && \
|
19
|
+
gem install -N rack webrick etc faraday yard aws-sdk-s3 redis mail && \
|
20
20
|
apk del build-deps && \
|
21
21
|
rm -rf /var/cache/apk/*
|
22
22
|
|
data/devel/run/base.rb
ADDED
@@ -0,0 +1,92 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# Investigative Case File System
|
4
|
+
#
|
5
|
+
# Copyright 2019 by Graham A. Field
|
6
|
+
#
|
7
|
+
# This program is free software: you can redistribute it and/or modify
|
8
|
+
# it under the terms of the GNU General Public License version 3.
|
9
|
+
#
|
10
|
+
# This program is distributed WITHOUT ANY WARRANTY; without even the
|
11
|
+
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
12
|
+
|
13
|
+
# frozen_string_literal: true
|
14
|
+
|
15
|
+
require 'faraday'
|
16
|
+
require 'aws-sdk-s3'
|
17
|
+
require 'redis'
|
18
|
+
|
19
|
+
require_relative '../../lib/icfs'
|
20
|
+
require_relative '../../lib/icfs/cache_elastic'
|
21
|
+
require_relative '../../lib/icfs/store_s3'
|
22
|
+
require_relative '../../lib/icfs/users_s3'
|
23
|
+
require_relative '../../lib/icfs/users_redis'
|
24
|
+
require_relative '../../lib/icfs/config_s3'
|
25
|
+
require_relative '../../lib/icfs/config_redis'
|
26
|
+
|
27
|
+
#################################################
|
28
|
+
# Get the API
|
29
|
+
#
|
30
|
+
def get_base
|
31
|
+
|
32
|
+
# the log
|
33
|
+
log = Logger.new(STDERR)
|
34
|
+
log.level = Logger::INFO
|
35
|
+
|
36
|
+
# S3
|
37
|
+
s3 = Aws::S3::Client.new(
|
38
|
+
endpoint: 'http://minio:9000',
|
39
|
+
access_key_id: 'minio_key',
|
40
|
+
secret_access_key: 'minio_secret',
|
41
|
+
force_path_style: true,
|
42
|
+
region: 'us-east-1'
|
43
|
+
)
|
44
|
+
|
45
|
+
# redis
|
46
|
+
redis = Redis.new(host: 'redis')
|
47
|
+
|
48
|
+
# elasic
|
49
|
+
es = Faraday.new('http://elastic:9200')
|
50
|
+
|
51
|
+
# default mapping
|
52
|
+
map = {
|
53
|
+
entry: 'entry',
|
54
|
+
case: 'case',
|
55
|
+
action: 'action',
|
56
|
+
index: 'index',
|
57
|
+
log: 'log',
|
58
|
+
lock: 'lock',
|
59
|
+
current: 'current',
|
60
|
+
}.freeze
|
61
|
+
|
62
|
+
# default config
|
63
|
+
defaults = {
|
64
|
+
'tz' => '-04:00'
|
65
|
+
}
|
66
|
+
|
67
|
+
# base objects
|
68
|
+
cache = ICFS::CacheElastic.new(map, es)
|
69
|
+
store = ICFS::StoreS3.new(s3, 'icfs', 'case/')
|
70
|
+
users_base = ICFS::UsersS3.new(s3, 'icfs', 'users/')
|
71
|
+
users = ICFS::UsersRedis.new(redis, users_base, {
|
72
|
+
prefix: 'users/',
|
73
|
+
expires: 60, # one minute cache for testing
|
74
|
+
log: log,
|
75
|
+
})
|
76
|
+
config_base = ICFS::ConfigS3.new(defaults, s3, 'icfs', 'config/')
|
77
|
+
config = ICFS::ConfigRedis.new(redis, config_base, {
|
78
|
+
prefix: 'config/',
|
79
|
+
expires: 60, # debug, only cache for one minute
|
80
|
+
})
|
81
|
+
api = ICFS::Api.new([], users, cache, store, config)
|
82
|
+
|
83
|
+
return {
|
84
|
+
cache: cache,
|
85
|
+
store: store,
|
86
|
+
users: users,
|
87
|
+
config: config,
|
88
|
+
api: api,
|
89
|
+
log: log,
|
90
|
+
}
|
91
|
+
|
92
|
+
end # def base
|
data/devel/run/copy-s3.rb
CHANGED
data/devel/run/email.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# Investigative Case File System
|
4
|
+
#
|
5
|
+
# Copyright 2019 by Graham A. Field
|
6
|
+
#
|
7
|
+
# This program is free software: you can redistribute it and/or modify
|
8
|
+
# it under the terms of the GNU General Public License version 3.
|
9
|
+
#
|
10
|
+
# This program is distributed WITHOUT ANY WARRANTY; without even the
|
11
|
+
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
12
|
+
|
13
|
+
# frozen_string_literal: true
|
14
|
+
|
15
|
+
require_relative 'base'
|
16
|
+
require_relative '../../lib/icfs/email/from'
|
17
|
+
require_relative '../../lib/icfs/email/basic'
|
18
|
+
|
19
|
+
# api
|
20
|
+
base = get_base
|
21
|
+
api = base[:api]
|
22
|
+
log = base[:log]
|
23
|
+
log.level = Logger::DEBUG
|
24
|
+
|
25
|
+
# load the email map
|
26
|
+
map_email = JSON.parse(File.read(ARGV[0]))
|
27
|
+
|
28
|
+
# email gateway
|
29
|
+
email_basic = ICFS::Email::Basic.new
|
30
|
+
email_from = ICFS::Email::From.new(map_email)
|
31
|
+
email = ICFS::Email::Core.new(api, log, [email_from, email_basic])
|
32
|
+
|
33
|
+
txt = STDIN.read
|
34
|
+
res = email.receive(txt)
|
35
|
+
|
36
|
+
p res
|
@@ -0,0 +1,43 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# Investigative Case File System
|
4
|
+
#
|
5
|
+
# Copyright 2019 by Graham A. Field
|
6
|
+
#
|
7
|
+
# This program is free software: you can redistribute it and/or modify
|
8
|
+
# it under the terms of the GNU General Public License version 3.
|
9
|
+
#
|
10
|
+
# This program is distributed WITHOUT ANY WARRANTY; without even the
|
11
|
+
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
12
|
+
|
13
|
+
# frozen_string_literal: true
|
14
|
+
|
15
|
+
# <app> <email_map.json> <config.json>
|
16
|
+
|
17
|
+
require_relative 'base'
|
18
|
+
require_relative '../../lib/icfs/email/from'
|
19
|
+
require_relative '../../lib/icfs/email/basic'
|
20
|
+
require_relative '../../lib/icfs/email/imap'
|
21
|
+
|
22
|
+
# api
|
23
|
+
base = get_base
|
24
|
+
api = base[:api]
|
25
|
+
log = base[:log]
|
26
|
+
log.level = Logger::DEBUG
|
27
|
+
|
28
|
+
# load the email map
|
29
|
+
map_email = JSON.parse(File.read(ARGV[0]))
|
30
|
+
|
31
|
+
# load the IMAP config
|
32
|
+
cfg_raw = JSON.parse(File.read(ARGV[1]))
|
33
|
+
cfg = {}
|
34
|
+
cfg_raw.each{|key, val| cfg[key.to_sym] = val}
|
35
|
+
|
36
|
+
# email gateway
|
37
|
+
email_basic = ICFS::Email::Basic.new
|
38
|
+
email_from = ICFS::Email::From.new(map_email)
|
39
|
+
email = ICFS::Email::Core.new(api, log, [email_from, email_basic])
|
40
|
+
imap = ICFS::Email::Imap.new(email, log, cfg)
|
41
|
+
|
42
|
+
# and fetch
|
43
|
+
imap.reconnect
|
@@ -0,0 +1,47 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# Investigative Case File System
|
4
|
+
#
|
5
|
+
# Copyright 2019 by Graham A. Field
|
6
|
+
#
|
7
|
+
# This program is free software: you can redistribute it and/or modify
|
8
|
+
# it under the terms of the GNU General Public License version 3.
|
9
|
+
#
|
10
|
+
# This program is distributed WITHOUT ANY WARRANTY; without even the
|
11
|
+
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
12
|
+
|
13
|
+
# <app> <srv_cert.pem> <srv_key.pem> <ca.pem> < <email.eml>
|
14
|
+
|
15
|
+
# frozen_string_literal: true
|
16
|
+
|
17
|
+
require_relative 'base'
|
18
|
+
require_relative '../../lib/icfs/email/smime'
|
19
|
+
require_relative '../../lib/icfs/email/basic'
|
20
|
+
|
21
|
+
# api
|
22
|
+
base = get_base
|
23
|
+
api = base[:api]
|
24
|
+
log = base[:log]
|
25
|
+
log.level = Logger::DEBUG
|
26
|
+
|
27
|
+
# load the email map
|
28
|
+
cert = ::OpenSSL::X509::Certificate.new(File.read(ARGV[0]))
|
29
|
+
key = ::OpenSSL::PKey.read(File.read(ARGV[1]))
|
30
|
+
ca = ::OpenSSL::X509::Store.new
|
31
|
+
ca.add_file(ARGV[2])
|
32
|
+
|
33
|
+
map_cn = {
|
34
|
+
'CN=client 1,OU=Test Client,OU=example,OU=org' => 'user1',
|
35
|
+
'CN=client 2,OU=Test Client,OU=example,OU=org' => 'user2',
|
36
|
+
'CN=client 3,OU=Test Client,OU=example,OU=org' => 'user3',
|
37
|
+
}
|
38
|
+
|
39
|
+
# email gateway
|
40
|
+
email_basic = ICFS::Email::Basic.new
|
41
|
+
email_smime = ICFS::Email::Smime.new(key, cert, ca, map_cn)
|
42
|
+
email = ICFS::Email::Core.new(api, log, [email_smime, email_basic])
|
43
|
+
|
44
|
+
txt = STDIN.read
|
45
|
+
res = email.receive(txt)
|
46
|
+
|
47
|
+
p res
|
data/devel/run/init-icfs.rb
CHANGED
data/devel/run/webrick.rb
CHANGED
@@ -10,68 +10,16 @@
|
|
10
10
|
# This program is distributed WITHOUT ANY WARRANTY; without even the
|
11
11
|
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
12
12
|
|
13
|
-
|
14
|
-
require 'aws-sdk-s3'
|
15
|
-
require 'redis'
|
13
|
+
# frozen_string_literal: true
|
16
14
|
|
17
|
-
require_relative '
|
18
|
-
require_relative '../../lib/icfs/cache_elastic'
|
19
|
-
require_relative '../../lib/icfs/store_s3'
|
20
|
-
require_relative '../../lib/icfs/users_s3'
|
21
|
-
require_relative '../../lib/icfs/users_redis'
|
15
|
+
require_relative 'base'
|
22
16
|
require_relative '../../lib/icfs/web/client'
|
23
|
-
require_relative '../../lib/icfs/web/config_s3'
|
24
|
-
require_relative '../../lib/icfs/web/config_redis'
|
25
17
|
require_relative '../../lib/icfs/demo/auth'
|
26
18
|
require_relative '../../lib/icfs/demo/static'
|
27
19
|
|
28
|
-
|
29
|
-
|
30
|
-
endpoint: 'http://minio:9000',
|
31
|
-
access_key_id: 'minio_key',
|
32
|
-
secret_access_key: 'minio_secret',
|
33
|
-
force_path_style: true,
|
34
|
-
region: 'us-east-1'
|
35
|
-
)
|
20
|
+
base = get_base()
|
21
|
+
api = base[:api]
|
36
22
|
|
37
|
-
# default mapping
|
38
|
-
map = {
|
39
|
-
entry: 'entry',
|
40
|
-
case: 'case',
|
41
|
-
action: 'action',
|
42
|
-
index: 'index',
|
43
|
-
log: 'log',
|
44
|
-
lock: 'lock',
|
45
|
-
current: 'current',
|
46
|
-
}.freeze
|
47
|
-
|
48
|
-
# default config
|
49
|
-
defaults = {
|
50
|
-
'tz' => '-04:00'
|
51
|
-
}
|
52
|
-
|
53
|
-
# the log
|
54
|
-
log = Logger.new(STDERR)
|
55
|
-
log.level = Logger::INFO
|
56
|
-
|
57
|
-
# base items
|
58
|
-
s3 = Aws::S3::Client.new
|
59
|
-
redis = Redis.new(host: 'redis')
|
60
|
-
es = Faraday.new('http://elastic:9200')
|
61
|
-
cache = ICFS::CacheElastic.new(map, es)
|
62
|
-
store = ICFS::StoreS3.new(s3, 'icfs'.freeze, 'case/'.freeze)
|
63
|
-
users_base = ICFS::UsersS3.new(s3, 'icfs'.freeze, 'users/'.freeze)
|
64
|
-
users = ICFS::UsersRedis.new(redis, users_base, {
|
65
|
-
prefix: 'users/'.freeze,
|
66
|
-
expires: 60, # one minute cache for testing
|
67
|
-
log: log,
|
68
|
-
})
|
69
|
-
api = ICFS::Api.new([], users, cache, store)
|
70
|
-
config_base = ICFS::Web::ConfigS3.new(defaults, s3, 'icfs', 'config/')
|
71
|
-
config = ICFS::Web::ConfigRedis.new(redis, config_base, {
|
72
|
-
prefix: 'config/',
|
73
|
-
expires: 60, # debug, only cache for one minute
|
74
|
-
})
|
75
23
|
web = ICFS::Web::Client.new('/static/icfs.css', '/static/icfs.js')
|
76
24
|
|
77
25
|
# static files
|
@@ -87,7 +35,7 @@ static = {
|
|
87
35
|
}
|
88
36
|
|
89
37
|
app = Rack::Builder.new do
|
90
|
-
use(ICFS::Demo::Auth, api
|
38
|
+
use(ICFS::Demo::Auth, api)
|
91
39
|
use(ICFS::Demo::Static, static)
|
92
40
|
run web
|
93
41
|
end
|