~tsileo/blobpad

756c96eaea252d9311ea26e509e90f9abf3b6a9a — Thomas Sileo 7 months ago cd405ce master
Add IndieAuth WIP
2 files changed, 157 insertions(+), 5 deletions(-)

M app.lua
M templates/view.html
M app.lua => app.lua +122 -2
@@ 4,8 4,14 @@ local json = require('json')
local router = require('router').new()
local ft = require('filetree')
local docstore = require('docstore')
local form = require('form')
local http = require('http')

local client = http.new()
client:headers():set('Accept', 'application/json')

local col = docstore.col('notes')
local published_notes = docstore.col('published_notes')

-- File upload endpoint used when creating a new note
router:post('/attachment', function(params)


@@ 82,20 88,134 @@ router:get('/', function(params)
  app.response:write(template.render('index.html', 'layout.html', { app_id = blobstash.app_id, stats = stats, qs = qs, collections = {}, docs = jdocs }))
end)

function get_micropub_endpoints(endpoint)
  if blobstash.app_cache["micropub:cached:" .. endpoint] then
    return {
      authorization_endpoint = blobstash.app_cache['micropub:authorization_endpoint:' .. endpoint],
      token_endpoint = blobstash.app_cache['micropub:token_endpoint:' .. endpoint],
      micropub = blobstash.app_cache['micropub:micropub:' .. endpoint],
      cached = true
    }
  end
  local rels = extra.parse_microformats(endpoint).rels
  local endpoints = { cached = false, endpoint = endpoint }
  if #rels.authorization_endpoint > 0 then
    endpoints.authorization_endpoint = rels.authorization_endpoint[1]
    blobstash.app_cache['micropub:authorization_endpoint:' .. endpoint] = endpoints.authorization_endpoint
  end
  if #rels.token_endpoint > 0 then
    endpoints.token_endpoint = rels.token_endpoint[1]
    blobstash.app_cache['micropub:token_endpoint:' .. endpoint] = endpoints.token_endpoint
  end
  if #rels.micropub > 0 then
    endpoints.micropub = rels.micropub[1]
    blobstash.app_cache['micropub:micropub:' .. endpoint] = endpoints.micropub
  end
  blobstash.app_cache["micropub:cached:" .. endpoint] = true
  return endpoints
end

local micropub_config = {}
if blobstash.app_config.micropub ~= nil and #blobstash.app_config.micropub.endpoints > 0 then
  micropub_config = get_micropub_endpoints(blobstash.app_config.micropub.endpoints[1])
end

router:get('/view', function(params)
  local cdoc = {}
  local args = app.request:args()

  local cid = args:get('_id')

  if cid == nil then
    app.response:set_status(404)
    app.response:write('no found')
    return
  end

  -- XXX v is a string cause a timestamp nano is too big for Lua numbers
  local v = args:get('v')
  if v == "" then
       v = "-1"
  end
  if cid ~= "" then
    cdoc, pointers = col:get(cid)
    cdoc, pointers = col:get(cid, v)
    _expand_note(cdoc, pointers, 800, false)
    cdoc._id = cid
  end

  app.response:write(template.render('view.html', 'layout.html', { app_id = blobstash.app_id, cid = cid, cdoc = cdoc, js = json.encode(cdoc), col = notes_col }))
  versions, vpointers = col:versions(cid)
  for _, v in ipairs(versions) do
    v.updated = extra.format_datetime(v._updated, '2006-01-02T15:04:05Z07:00', 'Jan 02, 2006 @ 15:04 MST')
  end
  app.response:write(template.render('view.html', 'layout.html', { app_id = blobstash.app_id, cid = cid, cdoc = cdoc, js = json.encode(cdoc), col = notes_col,
                                                                   versions = versions, micropub_config = micropub_config }))
end)

router:get('/conf', function(params)
  -- app.response:write(json.encode(url_for("/lol")))
  -- app.response:write(extra.parse_microformats(
  -- app.response:write(json.encode(extra.parse_microformats(blobstash.app_config.micropub.endpoints[1])))
  -- app.response:write(blobstash.app_base_url .. "\n" .. url_for("/lol"))
  -- app.response:write(json.encode(micropub_config))
  app.response:write(json.encode(blobstash.app_config))
end)

router:get('/ia', function(params)
  -- Generate a CSRF token
  local state = extra.random(12)
  blobstash.app_cache[state] = true

  -- Build the authorization request to the IndieAuth endpoint
  local f = form.new()
  f:set('me', blobstash.app_config.micropub.me)
  f:set('response_type', 'code')
  f:set('state', state)
  f:set('redirect_uri', url_for('/ia-redirect'))
  f:set('scope', 'create update')
  f:set('client_id', 'https://a4.io')  -- FIXME(tsileo): client ID for dev?
  app.response:redirect(micropub_config.authorization_endpoint .. "?" .. f:encode())
end)

router:get('/ia-redirect', function(params)
  local f = form.new()
  local args = app.request:args()

  -- Check the forwarded state to prevent CSRF attacks (i.e. someone faking an initiated request)
  if blobstash.app_cache[args:get('state')] ~= true then
    app.response:set_status(403)
    app.response:write("CSRF failed")
  end

  -- Exchange the code for a token (by calling the token endpoint)
  f:set('grant_type', 'authorization_code')
  f:set('code', args:get('code'))
  f:set('client_id', 'https://a4.io')
  f:set('redirect_uri', url_for('/ia-redirect'))
  f:set('me', blobstash.app_config.micropub.me)
  local resp, err = client:post(micropub_config.token_endpoint, {}, f)
  if err ~= nil then
    app.response:set_status(500)
    app.response:write("failed to retrieve token")
    return
  end

  -- Extract the access token
  local access_token = resp.body:json()["access_token"]

  -- Do a micropub request
  local c = http.new()
  c:headers():set("Authorization", "Bearer " .. access_token)
  c:headers():set("Accept", "application/json")
  c:headers():set("Content-Type", "application/json")
  local payload =  { ["type"] = {"h-entry"}, properties = { content = {"Hello"}, name = {"Test from BlobStash"} } }
  local resp, err = c:post(micropub_config.micropub_endpoint, {}, json.encode(payload))
  if err ~= nil then
    app.response:write("failed to post")
    return
 end

 app.response:write(resp.status_code .. resp.body:text() .. resp.headers:get("Location"))
end)

router:get('/new', function(params)
  local cdoc = {}

M templates/view.html => templates/view.html +35 -3
@@ 12,13 12,45 @@
    <div style="margin: 10px 0" class="note-controls-container">
    <div style="color:#555;padding:10px 0;" class="note-date">{{.cdoc.updated}}</div>
    <div style="text-align:right;flex-grow:1;" class="note-controls">
    <a href="/api/apps/{{$.app_id}}/new?_id={{.cdoc._id}}" class="button">Edit</a>
    <form method="POST" action="/api/apps/{{$.app_id}}/remove?cid={{.cdoc._id}}" style="display:inline;" onsubmit="return confirm('Confirm note deletion?');"><input type="submit" class="button" value="Remove"></form>
    <a href="/api/apps/{{$.app_id}}" class="button">Quit</a>
    <a href="{{ url_for "/new" }}?_id={{.cdoc._id}}" class="button">Edit</a>
    <form method="POST" action="{{ url_for "/remove" }}?cid={{.cdoc._id}}" style="display:inline;" onsubmit="return confirm('Confirm note deletion?');"><input type="submit" class="button" value="Remove"></form>
    <a href="{{url_for "/" }}" class="button">Quit</a>
	</div>
	</div>
	</div>


{{ if .micropub_config }}
<div class="note-container" style="word-wrap: break-word;border: 1px solid #ddd;padding:0 20px;margin:20px 0;">
<h3>Publishing on {{ .micropub_config.endpoint }}</h3>
    <div style="margin: 10px 0" class="note-controls-container">
    <div style="text-align:right;flex-grow:1;" class="note-controls">

    <a href="{{ url_for "/publish" }}?_id={{.cdoc._id}}" class="button">Publish</a>


    </div>
    </div>
 
</div>
{{ end }}

<div class="note-container" style="word-wrap: break-word;border: 1px solid #ddd;padding:0 20px;margin:20px 0;">
<h3>Versions</h3>
<ul style="list-style-type:none;padding-left:0;">
{{ range .versions }}
<li><a href="?_id={{._id}}&v={{._version}}" style="{{ if eq $.cdoc._version ._version }}color:#111;{{ else }}color:#555;{{end}}text-decoration:none;">{{.updated}}</a>
<span style="padding-left:15px;">
{{ if eq $.cdoc._version ._version }}
(currently viewing)
<a href="https://a4.io">published on hexa.ninja</a>
{{end}}
</span>
</li>
{{ end }}
<ul>
</div>
 
</div>

<script>