├── .gitignore ├── LICENSE.txt ├── README.md ├── bin ├── profile.js ├── status.js └── webfinger.js ├── index.js ├── lib └── ostatus │ ├── as.js │ ├── hcard.js │ ├── helper.js │ ├── http.js │ ├── index.js │ ├── push.js │ ├── salmon.js │ ├── templates │ ├── hcard.html.mu │ ├── hostXrd.xml.mu │ ├── updates.xml.mu │ ├── user.html.mu │ └── userXrd.xml.mu │ └── webfinger.js ├── package.json └── tests ├── test-as.js ├── test-as.xml ├── test-minime.js ├── test-openssl.js ├── test-salmon.input ├── test-salmon.js ├── test-salmon.output └── test-statusnet.js /.gitignore: -------------------------------------------------------------------------------- 1 | .project 2 | .settings/ 3 | node_modules 4 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2010 Laurent Eschenauer 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. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | node-ostatus 2 | ============ 3 | 4 | An implementation of the [OStatus](http://ostatus.org) protocol stack for node.js 5 | 6 | Copyright (C) 2010 Laurent Eschenauer 7 | 8 | ** Ongoing development branch, unstable code, use at your own risk :-) ** 9 | 10 | Install 11 | ------- 12 | 13 | If you are using npm (the node packet manager), installing is as easy as: 14 | npm install ostatus 15 | 16 | 17 | Dependencies 18 | ------------ 19 | 20 | If you are using this from source, you can install the dependencies using npm from the root of the source folder: 21 | npm bundle 22 | 23 | Or you can install all dependencies manually: 24 | * [node](http://nodejs.org/) v4+ is required, I'm developing against trunk. 25 | * [node-o3-xml](https://github.com/ajaxorg/node-o3-xml/) 26 | * [Mu](https://github.com/raycmorgan/Mu/tree/v2) the v2 branch 27 | * [Flow](https://github.com/willconant/flow-js) 28 | * [Base64](https://github.com/pkrumins/node-base64) 29 | 30 | Documentation 31 | ------------- 32 | 33 | The API is documented in the [wiki](http://github.com/eschnou/node-ostatus/wiki). 34 | 35 | Support 36 | ------- 37 | 38 | Please avoid sending an email directly to me. Instead, involve also other users by: 39 | 40 | - Filling an issue report in the [issue tracker](https://github.com/eschnou/node-ostatus/issues). 41 | - Asking your question on the [Ostatus](http://groups.google.com/group/ostatus-discuss) or [NodeJS](http://groups.google.com/group/nodejs) mailing list. 42 | - Ping me on twitter [@eschnou](http://twitter.com/eschnou) <== Ho irony :-) 43 | 44 | Contribute 45 | ---------- 46 | 47 | Want to help ? That's awesome ! I'm doing this just a couple hours a week, between a full-time job 48 | and a six months old baby :-) So, of course, any help is welcome ! What you can do: 49 | 50 | - Try it out. With the command line, or in a project, and let me know what works, what does not. We need 51 | to get the interoperability right. Probably a lot of edge cases remain to be solved. 52 | - Use it in a project and provide feedback on the API. Does it fit you need ? What else do you need ? 53 | If the documentation is weak, don't hesitate to edit the wiki and make it better. 54 | - Want to add a feature or refactor some code (see the todo list below). Then the best is to first get in 55 | touch with me, since I'm actively working on it, you don't want to waste your time on something I did already. 56 | 57 | I accept pull-requests, but would appreciate if: 58 | 59 | - You package it on a separate feature branch and make sure it is rebased on top of the dev branch. 60 | - Do not merge the upstream with your work, instead you should rebase your work on top of the upstream. 61 | - Use one branch for one feature. 62 | 63 | Client 64 | ------ 65 | 66 | In the bin/ folder, you'll find a few simple command line clients for ostatus. If you install with NPM, 67 | these will be linked and added to your path. 68 | 69 | ### Status 70 | Display the last status update of someone: 71 | status eschnou@identi.ca 72 | 73 | Output: 74 | 2011-03-06T14:25:08+00:00 75 | One day, I'll post these kind of updates from my own host. In the meanwhile, cheers ! 76 | 77 | ### Profile 78 | Display the profile of someone 79 | profile eschnou@identi.ca 80 | 81 | Output: 82 | photo: 'http://avatar.identi.ca/16106-96-20080722053859.png', 83 | nickname: 'eschnou', 84 | fn: 'Laurent', 85 | label: 'Liège, Belgium', 86 | url: 'http://eschnou.com', 87 | note: 'Coder, not blogger. Technology enthusiast. Exploring the future of the web & mobility.' 88 | 89 | Progress 90 | -------- 91 | 92 | The following pieces of the protocol are implemented: 93 | 94 | - [webfinger](http://code.google.com/p/webfinger/): 95 | * Lookup a user account and return the user meta in a JSON format 96 | * Rendering of host/user meta based on a JSON input object 97 | - [pubsubhubbub](http://code.google.com/p/pubsubhubbub/): 98 | * Subscribe/Unsubscribe to a topic on another hub 99 | * Verify a subscription request from an other hub 100 | * Distribute content to subscribers (with support for authenticated content distribution) 101 | - [hcard](http://microformats.org/wiki/hcard): 102 | * Lookup and parse a hcard into a JSON object 103 | * Render an hcard from a JSON object 104 | - [activitystreams](http://activitystrea.ms/): 105 | * Fetch an atom feed with activitystream content and return a JSON representation of the stream. The JSON object is a valid activitystream JSON object. 106 | * Render an atom feed from an array of activities in JSON 107 | - [salmon](http://www.salmon-protocol.org/) 108 | * Unpacking envelope and base64url encoding/decoding 109 | 110 | What is missing: 111 | 112 | - Magic envelope signature generation and verification. 113 | - Activitystream stuff is basic and maybe outdated. 114 | 115 | 116 | My ToDo list 117 | ------------ 118 | 119 | - Implement Salmon crypto stuff. Unfortunately the node crypto lib is not enough, 120 | and a new RSA lib is required. Either full javascript or C++ 121 | - Complete the activitystream stuff, ensuring mapping between XML and internal 122 | JSON data structure. 123 | - Lot's of edge cases and interop issues to fix. 124 | - Get rid of o3-xml and use a Sax parser instead. 125 | - Get rid of the templates and generate the XML programaticaly. 126 | - Write more test cases. 127 | 128 | 129 | Acknowledgments 130 | --------------- 131 | This code is a 100% rewrite by myself, however: 132 | 133 | - The idea of using Mustache templates for OStatus payload come from the [ostatus-js](https://github.com/maxogden/ostatus-js) project by maxodgen. 134 | - The webfinger implementation is inspired by the [node-webfinger](https://github.com/banksean/node-webfinger) implementation of banksean. 135 | 136 | 137 | License 138 | ------- 139 | 140 | The MIT License 141 | 142 | Copyright (c) 2010 Laurent Eschenauer 143 | 144 | Permission is hereby granted, free of charge, to any person obtaining a copy 145 | of this software and associated documentation files (the "Software"), to deal 146 | in the Software without restriction, including without limitation the rights 147 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 148 | copies of the Software, and to permit persons to whom the Software is 149 | furnished to do so, subject to the following conditions: 150 | 151 | The above copyright notice and this permission notice shall be included in 152 | all copies or substantial portions of the Software. 153 | 154 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 155 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 156 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 157 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 158 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 159 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 160 | THE SOFTWARE. 161 | -------------------------------------------------------------------------------- /bin/profile.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | /* 3 | * node-ostatus - An implementation of OStatus for node.js 4 | * 5 | * Copyright (C) 2010 Laurent Eschenauer 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy 8 | * of this software and associated documentation files (the "Software"), to deal 9 | * in the Software without restriction, including without limitation the rights 10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | * copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in 15 | * all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | * THE SOFTWARE. 24 | */ 25 | 26 | var Ostatus = require('ostatus'), 27 | Hcard = Ostatus.hcard, 28 | Webfinger = Ostatus.webfinger; 29 | 30 | var _main = function(argv) { 31 | // Parse the command line arguments 32 | if (argv.length < 3) { 33 | console.log("Usage: profile [identifier]"); 34 | process.exit(-1); 35 | } 36 | 37 | // Webfinger require acct: identifier, we add if required 38 | var identifier = argv[2]; 39 | if (identifier.length<5 || identifier.substring(0,5) != "acct:") { 40 | identifier = "acct:" + identifier; 41 | } 42 | 43 | // Wrap the request in a try.. catch to nicely die on errors 44 | try { 45 | Ostatus.profile(argv[2], function(err, result) { 46 | if (err) return _error(err); 47 | _result(result); 48 | }); 49 | } catch (error) { 50 | _error(error); 51 | } 52 | }; 53 | 54 | var _error = function(error) { 55 | console.log("Error: " + error.message); 56 | process.exit(-1); 57 | }; 58 | 59 | var _result = function(result) { 60 | if (result != undefined) { 61 | for(key in result) { 62 | console.log(key + ": " + result[key]); 63 | } 64 | console.log(result); 65 | } 66 | }; 67 | 68 | _main(process.argv); 69 | -------------------------------------------------------------------------------- /bin/status.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | /* 3 | * node-ostatus - An implementation of OStatus for node.js 4 | * 5 | * Copyright (C) 2010 Laurent Eschenauer 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy 8 | * of this software and associated documentation files (the "Software"), to deal 9 | * in the Software without restriction, including without limitation the rights 10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | * copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in 15 | * all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | * THE SOFTWARE. 24 | */ 25 | 26 | var Ostatus = require('ostatus'), 27 | Util = require('util'); 28 | 29 | var _main = function(argv) { 30 | // Parse the command line arguments 31 | if (argv.length < 3) { 32 | console.log("Usage: status [identifier]"); 33 | process.exit(-1); 34 | } 35 | 36 | // Wrap the request in a try.. catch to nicely die on errors 37 | try { 38 | Ostatus.activities(argv[2], function(err, result) { 39 | if (err) _error(err); 40 | _result(result); 41 | }); 42 | } catch (error) { 43 | _error(error); 44 | } 45 | }; 46 | 47 | var _error = function(error) { 48 | console.log("Error: " + error); 49 | process.exit(-1); 50 | }; 51 | 52 | var _result = function(feed) { 53 | console.log(feed); 54 | if (feed) { 55 | var entry = feed.entries[0]; 56 | var time = entry['updated']; 57 | var title = entry['title']; 58 | console.log(time + "\n" + title); 59 | } 60 | }; 61 | 62 | _main(process.argv); 63 | -------------------------------------------------------------------------------- /bin/webfinger.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | /* 3 | * node-ostatus - An implementation of OStatus for node.js 4 | * 5 | * Copyright (C) 2010 Laurent Eschenauer 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy 8 | * of this software and associated documentation files (the "Software"), to deal 9 | * in the Software without restriction, including without limitation the rights 10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | * copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in 15 | * all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | * THE SOFTWARE. 24 | */ 25 | 26 | var Ostatus = require('ostatus'), 27 | Util = require('util'), 28 | Hcard = Ostatus.hcard, 29 | Webfinger = Ostatus.webfinger; 30 | 31 | var _main = function(argv) { 32 | // Parse the command line arguments 33 | if (argv.length < 3) { 34 | console.log("Usage: finger [account]"); 35 | process.exit(-1); 36 | } 37 | 38 | // Wrap the request in a try.. catch to nicely die on errors 39 | try { 40 | 41 | // Webfinger require acct: reference, we add if required 42 | var reference = argv[2]; 43 | 44 | // TODO this is not clean, if no prefix is provided, I should try to discover if it is a http or acct uri. 45 | if (reference.length<5 || (reference.substring(0,5) != "acct:" && reference.substring(0,5) != "http:")) { 46 | reference = "acct:" + reference; 47 | } 48 | Webfinger.lookup(reference, _result); 49 | } catch (error) { 50 | _error(error); 51 | } 52 | }; 53 | 54 | var _error = function(error) { 55 | console.log("Error: " + error.message); 56 | process.exit(-1); 57 | }; 58 | 59 | var _result = function(error, result) { 60 | if (error) { 61 | _error(error); 62 | } else { 63 | console.log(Util.inspect(result)); 64 | } 65 | }; 66 | 67 | _main(process.argv); 68 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * node-ostatus - An implementation of OStatus for node.js 3 | * 4 | * Copyright (C) 2010 Laurent Eschenauer 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | 25 | module.exports = require('./lib/ostatus'); 26 | -------------------------------------------------------------------------------- /lib/ostatus/as.js: -------------------------------------------------------------------------------- 1 | /* 2 | * node-ostatus - An implementation of OStatus for node.js 3 | * 4 | * Copyright (C) 2010 Laurent Eschenauer 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | 25 | var Sys = require('util'), 26 | Url = require('url'), 27 | Flow = require('flow'), 28 | Util = require('util'), 29 | Http = require('./http.js'), 30 | Sax = require('sax'); 31 | Path = require('path'), 32 | Mu = require('mu'); 33 | 34 | function render(host, updates, profile, callback) { 35 | Mu.compile('updates.xml.mu', function (err, parsed) { 36 | if (err) return callback(err); 37 | var buffer = ''; 38 | var context = profile; 39 | context.updates = updates; 40 | context.host = host; 41 | if (updates && updates.length > 0) context.updated = updates[0].updated; 42 | Mu.render(parsed,context) 43 | .on('data', function (c) { buffer += c.toString(); }) 44 | .on('end', function () {callback(null, buffer);}) 45 | .on('error', function(err) {callback(err, buffer);}); 46 | }); 47 | } 48 | 49 | function fromUrl(url, callback) { 50 | console.log("Requesting atom feed at " + url); 51 | var feedParser; 52 | Flow.exec( 53 | function() { 54 | Http.request(url,false,'GET',null,function() { 55 | feedParser = new FeedParser(); 56 | return function(s) { 57 | feedParser.write(s); 58 | }; 59 | }, this); 60 | }, 61 | function(err,response,body) { 62 | if (feedParser) feedParser.end(); 63 | if (err) return callback(err); 64 | if (response.statusCode != 200) return callback(new Error("Http request returned status " + response.statusCode)); 65 | /*fromXml(body, this);*/ 66 | this(null, feedParser.feed); 67 | }, 68 | function(err, activities) { 69 | if (err) return callback(err); 70 | activities.url = url; 71 | callback(null, activities); 72 | } 73 | ); 74 | } 75 | 76 | function FeedParser() { 77 | var feed = this.feed = { links: [], entries: [] }; 78 | 79 | var treeNames = [], elText, entry; 80 | this.parser = new Sax.parser(); 81 | this.parser.onopentag = function(node) { 82 | treeNames.push(node.name); 83 | elText = ''; 84 | 85 | if (treeNames[0] === 'FEED') { 86 | if (treeNames[1] === 'LINK' && treeNames.length < 2) 87 | feed.push(node.attributes); 88 | else if (treeNames[1] === 'ENTRY') { 89 | if (treeNames.length === 2) { 90 | feed.entries.push(entry = { links: [] }); 91 | } else if (treeNames[2] === 'LINK' && treeNames.length === 3) { 92 | entry.links.push(node.attributes); 93 | } else if (treeNames[2] === 'AUTHOR') { 94 | if (treeNames.length === 3) 95 | entry.author = { links: [] }; 96 | else if (treeNames[3] === 'LINK' && treeNames.length === 4) 97 | entry.author.links.push(node.attributes); 98 | } 99 | } 100 | } 101 | }; 102 | this.parser.ontext = function(s) { 103 | elText += s; 104 | }; 105 | this.parser.oncdata = function(s) { 106 | elText += s; 107 | }; 108 | this.parser.onclosetag = function() { 109 | if (treeNames[0] === 'FEED') { 110 | switch(treeNames[1]) { 111 | case 'TITLE': 112 | feed.title = elText; 113 | break; 114 | case 'SUBTITLE': 115 | feed.subtitle = elText; 116 | break; 117 | case 'UPDATED': 118 | feed.updated = elText; 119 | break; 120 | case 'ENTRY': 121 | switch(treeNames[2]) { 122 | case 'ID': 123 | entry.id = elText; 124 | break; 125 | case 'TITLE': 126 | entry.title = elText; 127 | break; 128 | case 'VERB': 129 | entry.verb = elText; 130 | break; 131 | case 'TYPE': 132 | entry.type = elText; 133 | break; 134 | case 'CONTENT': 135 | entry.content = elText; 136 | break; 137 | case 'UPDATED': 138 | entry.updated = elText; 139 | break; 140 | case 'AUTHOR': 141 | switch(treeNames[3]) { 142 | case 'OBJECT-TYPE': 143 | entry.author.type = elText; 144 | break; 145 | case 'URI': 146 | entry.author.uri = elText; 147 | break; 148 | case 'NAME': 149 | entry.author.name = elText; 150 | break; 151 | } 152 | } 153 | break; 154 | } 155 | } 156 | 157 | elText = ''; 158 | treeNames.pop(); 159 | }; 160 | }; 161 | FeedParser.prototype.write = function(s) { 162 | this.parser.write(s); 163 | }; 164 | FeedParser.prototype.end = function() { 165 | this.parser.close(); 166 | }; 167 | 168 | 169 | 170 | exports.fromUrl = fromUrl; 171 | //exports.parseFeed = parseFeed; 172 | //exports.parseEntry = parseEntry; 173 | exports.render = render; -------------------------------------------------------------------------------- /lib/ostatus/hcard.js: -------------------------------------------------------------------------------- 1 | /* 2 | * node-ostatus - An implementation of OStatus for node.js 3 | * 4 | * Copyright (C) 2010 Laurent Eschenauer 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | 25 | var Sys = require('util'), 26 | Url = require('url'), 27 | Http = require('./http.js'), 28 | Sax = require('sax'), 29 | Path = require('path'), 30 | Mu = require('mu'); 31 | 32 | /* 33 | * Lookup for HCard content at the given profile url. 34 | * 35 | * The page is parsed for hcard markup and a JSON profile object is returned. 36 | * 37 | * @param {String} url 38 | * the url of the profile page containing some hcard markup 39 | * @param {Function} callback 40 | * a callback function in the form function(err, result) 41 | * 42 | * TODO Support for HTML pages that are not valid XML. 43 | */ 44 | function lookup(url, callback) { 45 | console.log("Requesting hcard at " + url); 46 | Http.get(url, function(err, response, body) { 47 | if (response.statusCode == 200) { 48 | var vcards = _findVcards(body); 49 | if (vcards.length > 0) { 50 | return callback(null, vcards); 51 | } else { 52 | callback(new Error("No HCard content in this profile page")); 53 | } 54 | } else { 55 | callback(new Error("HCard lookup returned HTTP status " + response.statusCode)); 56 | } 57 | }); 58 | } 59 | 60 | /* 61 | * Render a HCard HTML page from a JSON profile object. In practice, the profile 62 | * is simply used as input to the hcard.html.mu template. 63 | */ 64 | function render(profile, callback) { 65 | Mu.compile('hcard.html.mu', function (err, parsed) { 66 | if (err) return callback(err); 67 | 68 | var buffer = ''; 69 | Mu.render(parsed, profile) 70 | .on('data', function (c) { buffer += c.toString(); }) 71 | .on('end', function () { 72 | callback(null, buffer); 73 | }); 74 | }); 75 | } 76 | 77 | ///// Private functions 78 | function _findVcards(content) { 79 | var vcardChildDepth = 0, fields; 80 | var vcards = [], vcard; 81 | var parser = Sax.parser(); 82 | parser.onopentag = function(node) { 83 | if (node.attributes['class'] && 84 | node.attributes['class'].search(/(^|\s)vcard(\s|$)/i)>=0) { 85 | 86 | vcardChildDepth = 1; 87 | vcards.push(vcard = {}); 88 | } else if (vcardChildDepth > 0) { 89 | vcardChildDepth++; 90 | } 91 | 92 | if (vcardChildDepth > 0) { 93 | fields = node.attributes['class'] && 94 | node.attributes['class'].split(/\s/).filter(function(field) { 95 | return elements.indexOf(field); 96 | }) || 97 | []; 98 | } 99 | }; 100 | parser.ontext = function(s) { 101 | if (vcardChildDepth > 0) { 102 | fields.forEach(function(field) { 103 | vcard[field] = s.trim(); 104 | }); 105 | } 106 | }; 107 | parser.onclosetag = function() { 108 | if (vcardChildDepth > 0) 109 | vcardChildDepth--; 110 | }; 111 | 112 | parser.write(content).close(); 113 | return vcards; 114 | } 115 | 116 | var elements = [ 117 | "fn", 118 | "n", 119 | "adr", 120 | "agent", 121 | "bday", 122 | "category", 123 | "class", 124 | "email", 125 | "geo", 126 | "key", 127 | "label", 128 | "logo", 129 | "mailer", 130 | "nickname", 131 | "note", 132 | "org", 133 | "photo", 134 | "avatar", 135 | "rev", 136 | "role", 137 | "sort-string", 138 | "sound", 139 | "tel", 140 | "title", 141 | "tz", 142 | "uid", 143 | "url", 144 | ]; 145 | 146 | exports.lookup = lookup; 147 | exports.render = render; -------------------------------------------------------------------------------- /lib/ostatus/helper.js: -------------------------------------------------------------------------------- 1 | /* 2 | * node-ostatus - An implementation of OStatus for node.js 3 | * 4 | * Copyright (C) 2010 Laurent Eschenauer 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | 25 | var Hcard = require("./hcard") 26 | , As = require("./as") 27 | , Webfinger = require("./webfinger"); 28 | 29 | function activities(identifier, callback) { 30 | // Fix arguments 31 | if (identifier.length<5 || (identifier.substring(0,5) != "acct:" && identifier.substring(0,5) != "http:")) { 32 | identifier = "acct:" + identifier; 33 | } 34 | 35 | // Perform a webfinger lookup on the identifier, 36 | // then search for the activities link and if there is one, 37 | // fetch the feed and parse it to Json. 38 | Webfinger.lookup(identifier, function(err, result) { 39 | if (err) callback(err); 40 | var links = result.links; 41 | for (i=0;i 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | 25 | var Http = require('http'), 26 | Https = require('https'), 27 | Url = require('url'); 28 | 29 | function get(url, callback, headers) { 30 | request(url, false, 'GET', headers, null, callback); 31 | } 32 | 33 | function post(url, reqBody, headers, callback) { 34 | request(url, reqBody, 'POST', headers, null, callback); 35 | } 36 | 37 | function request(url, data, method, headers, parserGen, callback) { 38 | var body; 39 | var url = Url.parse(url); 40 | var host = url.hostname; 41 | var path = url.pathname; 42 | var headers = headers ? headers : {}; 43 | var secure = (url.protocol == "https:") ? true : false; 44 | var port = secure ? 443 : 80; 45 | var client = secure ? Https : Http; 46 | 47 | // Add the mandatory HTTP Headers 48 | headers["host"] = secure ? host + ":443" : host; 49 | headers["user-agent"] = "node-ostatus"; 50 | 51 | // If we post data, add a content length header 52 | // TODO The +1 is a hack, need to be fixed 53 | if (data) { 54 | headers["Content-Length"] = data.length + 1; 55 | } 56 | 57 | // Add the query string to the path if any 58 | if (url.search != undefined) path += url.search; 59 | 60 | // Assemble the request options 61 | var options = { 62 | host: host, 63 | port: port, 64 | path: path, 65 | method: method, 66 | headers: headers 67 | }; 68 | 69 | // Proceed with the request 70 | var request = client.request(options, function (response) { 71 | if (parserGen) { 72 | var parser = parserGen(); 73 | } else { 74 | body = ''; 75 | } 76 | response.setEncoding('utf8'); 77 | response.on('data', function (chunk) { 78 | if (parser) 79 | parser(chunk); 80 | else 81 | body += chunk; 82 | }); 83 | response.on('end', function (chunk) { 84 | callback(null, response, body); 85 | }); 86 | }).on('error', function(error) { 87 | console.log("HTTP request returned error: " + error.message); 88 | callback(error); 89 | }); 90 | 91 | // Send the request 92 | if (data) { 93 | request.write(data); 94 | } 95 | request.end(); 96 | } 97 | 98 | function response(res, message, code, type) { 99 | var body = message; 100 | var type = type ? type : 'text/html'; 101 | var code = code ? code : 200; 102 | res.writeHead(code, { 103 | 'Content-Type': type + ";charser=UTF-8", 104 | 'Content-Length': body.length 105 | }); 106 | if (body) res.write(body); 107 | res.end(); 108 | } 109 | 110 | exports.get = get; 111 | exports.post = post; 112 | exports.request = request; 113 | exports.response = response; -------------------------------------------------------------------------------- /lib/ostatus/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * node-ostatus - An implementation of OStatus for node.js 3 | * 4 | * Copyright (C) 2010 Laurent Eschenauer 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | 25 | var helper = require('./helper.js') 26 | , Mu = require('mu'); 27 | 28 | // Setup the Mu template paths 29 | Mu.root = Path.join(__dirname,'./templates'); 30 | 31 | // Exports 32 | exports.version = "0.2.0dev"; 33 | exports.as = require('./as.js'); 34 | exports.push = require('./push.js'); 35 | exports.hcard = require('./hcard.js'); 36 | exports.webfinger = require('./webfinger.js'); 37 | exports.salmon = require('./salmon.js'); 38 | exports.activities = helper.activities; 39 | exports.profile = helper.profile; 40 | -------------------------------------------------------------------------------- /lib/ostatus/push.js: -------------------------------------------------------------------------------- 1 | /* 2 | * node-ostatus - An implementation of OStatus for node.js 3 | * 4 | * Copyright (C) 2010 Laurent Eschenauer 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | 25 | var Sys = require('util'), 26 | Url = require('url'), 27 | Crypto = require('crypto'), 28 | Http = require('./http.js'), 29 | Util = require('util'), 30 | Qs = require('querystring'); 31 | 32 | 33 | function verify(request, callback) { 34 | var subscriber = request["hub.callback"]; 35 | var challenge = _secret(12); 36 | var query = { 37 | "hub.mode": request["hub.mode"], 38 | "hub.topic": request["hub.topic"], 39 | "hub.challenge": challenge, 40 | "hub.lease_seconds": 60 * 60 // one hour lease for now 41 | }; 42 | 43 | if (request["hub.verify_token"] != undefined) { 44 | query["hub.verify_token"] = request["hub.verify_token"]; 45 | } 46 | 47 | var url = subscriber + "?" + Qs.stringify(query); 48 | 49 | Http.get(url, function(err, response, body) { 50 | if (err) return callback(err); 51 | if (body == challenge) { 52 | callback(null, request["hub.topic"]); 53 | } else { 54 | callback(new Error("Challenge did not match, expecting " + challenge + " but received " + body)); 55 | } 56 | }); 57 | } 58 | 59 | function subscribe(url, config, callback) { 60 | var data = Qs.stringify(config); 61 | var headers = {"Content-Type": "application/x-www-form-urlencoded"}; 62 | console.log("Url: " + url); 63 | console.log("Data: " + data); 64 | Http.post(url, data, headers, function(err, response, body) { 65 | if (err) return callback(err); 66 | if (response.statusCode == 202) { 67 | callback(null, "pending"); 68 | } else if (response.statusCode == 204) { 69 | callback(null, "accepted"); 70 | } else { 71 | callback(new Error("Push subscribe http error " + response.statusCode + "\n" + body)); 72 | } 73 | }); 74 | } 75 | 76 | function sign(data, secret) { 77 | // Since we'll push the data over HTTP in UTF-8, 78 | // we encode the data to a utf-8 buffer fot the hmac. 79 | var buffer = new Buffer(data, 'utf-8'); 80 | var hmac = Crypto.createHmac("sha1", secret); 81 | var hash = hmac.update(buffer); 82 | return hmac.digest(encoding="hex"); 83 | } 84 | 85 | function distribute(data, url, secret, callback) { 86 | var headers = {"Content-Type": "application/atom-xml"}; 87 | 88 | // I guess someone adds one of these and this is why I need to take 89 | // it into account for the hmac. 90 | data = data + "\r"; 91 | 92 | //console.log("Data: ===" + data + "==="); 93 | //console.log("Secret: ===" + secret + "==="); 94 | //console.log("Data size: " + data.length); 95 | 96 | if (secret != undefined) { 97 | var digest = "sha1="+ sign(data , secret); 98 | headers["X-Hub-Signature"] = digest; 99 | console.log("Digest: " + digest); 100 | } 101 | 102 | Http.post(url, data, headers, function(err, response, body) { 103 | if (err) return callback(err); 104 | if (response.statusCode >= 200 && response.statusCode < 300) { 105 | callback(null, response.statusCode, body); 106 | } else { 107 | callback(new Error("Push distribute returned HTTP Status " + response.statusCode)); 108 | } 109 | }); 110 | } 111 | 112 | function _secret(size) { 113 | var text = ""; 114 | var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; 115 | 116 | for( var i=0; i < size; i++ ) 117 | text += possible.charAt(Math.floor(Math.random() * possible.length)); 118 | 119 | return text; 120 | } 121 | 122 | exports.verify = verify; 123 | exports.distribute = distribute; 124 | exports.subscribe = subscribe; 125 | exports.sign = sign; -------------------------------------------------------------------------------- /lib/ostatus/salmon.js: -------------------------------------------------------------------------------- 1 | /* 2 | * node-ostatus - An implementation of OStatus for node.js 3 | * 4 | * Copyright (C) 2010 Laurent Eschenauer 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | 25 | var Sys = require('util'), 26 | Url = require('url'), 27 | Flow = require('flow'), 28 | Util = require('util'), 29 | Http = require('./http.js'), 30 | Xml = require('o3-xml'); 31 | Path = require('path'), 32 | Mu = require('mu'); 33 | Base64 = require('base64'); 34 | Crypto = require('crypto'); 35 | Bn = require('bignumber'); 36 | Ostatus = require('ostatus'); 37 | Buffer = require('buffer').Buffer; 38 | 39 | /* 40 | * Parse an incoming salmon message, and attempt to verify 41 | * the signature. 42 | */ 43 | function unpack(body, callback) { 44 | xmlToJs(body, function(err, result) { 45 | if (err) return callback(err); 46 | // Keep a reference to the magic enveloppe 47 | var me = result; 48 | // Decode the activity entry from the enveloppe 49 | var entry = Ostatus.salmon.base64url_decode(result.data); 50 | // Parse the entry XML to JSON 51 | Ostatus.as.parseEntry(entry, function(err, result) { 52 | if (err) return callback(err); 53 | // Keep a reference to the JSON activity 54 | var activity = result; 55 | // We need an actor URI to validate the signature 56 | if (activity.actor == undefined && activity.actor.uri == undefined) { 57 | return callback(new Error("Missing actor URI in activity entry"), false, activity); 58 | } 59 | // Keep a reference to the actor uri 60 | var uri = activity.actor.uri; 61 | // Webfinger to retrieve the actor public key 62 | Ostatus.webfinger.lookup(uri, function(err, result) { 63 | if (err) return callback(err, false, activity); 64 | // Lookup the key from the webfinger JRD 65 | var key = _grabKey(result); 66 | if (!key) return callback(new Error("Could not find a key in user XRD"), false, activity); 67 | // We need the second part of the key 68 | key = key.href.split(','); 69 | // Verify the signature (synchronous) 70 | var result = Ostatus.salmon.verify_signature(me, key[1]); 71 | return callback(false, result, activity); 72 | }); 73 | }); 74 | }); 75 | } 76 | 77 | function xmlToJs(body, callback) { 78 | try { 79 | var doc = Xml.parseFromString(body); 80 | var childNodes = doc.documentElement.childNodes; 81 | var result = {}; 82 | result.sigs = []; 83 | for ( var i = 0; i < childNodes.length; i++) { 84 | var node = childNodes[i]; 85 | var name = node.nodeName; 86 | if (name == "data") { 87 | result.data = node.nodeValue; 88 | if (attribute = node.attributes.getNamedItem("type")) result["data_type"] = attribute.value; 89 | } else if (name == "sig") { 90 | var sig = {"value": node.nodeValue}; 91 | if (attribute = node.attributes.getNamedItem("key_id")) sig["key_id"] = attribute.value; 92 | result.sigs.push(sig); 93 | } else { 94 | var value = node.nodeValue; 95 | result[name] = value; 96 | } 97 | } 98 | callback(null, result); 99 | } catch (exception) { 100 | callback(exception); 101 | } 102 | } 103 | 104 | function verify_signature(me, key) { 105 | // Assemble the signature base string 106 | var M = me.data + "." + base64url_encode(me.data_type, 'ascii') + "." + base64url_encode(me.encoding, 'ascii') + "." + base64url_encode(me.alg, 'ascii'); 107 | //console.log("M: " + M); 108 | console.log("Key: " + key); 109 | 110 | // Compute the SHA256 digest hash 111 | var sha256 = Crypto.createHash('sha256'); 112 | sha256.update(M); 113 | var hash = sha256.digest('hex'); 114 | console.log("Hash: " + hash); 115 | 116 | // Decode the signature from the base64url encoded value we have in the envelope 117 | var sig = Bn.b64toBA(me.sigs[0].value.replace(/-/g, '+').replace(/_/g, '/')); 118 | var t = ""; 119 | for(var i=0; i 2 | 3 | 4 | 5 | HCard - {{username}} 6 | 7 | 8 |
9 |
10 |

User profile

11 |
12 |
Photo
13 |
14 | {{username}} 15 |
16 |
17 |
18 |
Nickname
19 |
20 | {{username}} 21 |
22 |
23 |
24 |
Full name
25 |
26 | {{fullname}} 27 |
28 |
29 |
30 |
Location
31 |
{{location}}
32 |
33 |
34 |
Note
35 |
{{note}}
36 |
37 |
38 |
39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /lib/ostatus/templates/hostXrd.xml.mu: -------------------------------------------------------------------------------- 1 | 2 | 4 | {{host}} 5 | 7 | Resource Descriptor 8 | 9 | -------------------------------------------------------------------------------- /lib/ostatus/templates/updates.xml.mu: -------------------------------------------------------------------------------- 1 | 2 | 3 | http://{{host}}/updates/{{username}}.atom 4 | Latest updates from {{fullname}} 5 | {{updated}} 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | {{fullname}} 15 | http://{{host}}/users/{{username}} 16 | 17 | 18 | http://activitystrea.ms/schema/1.0/person 19 | http://{{host}}/users/{{username}} 20 | {{fullname}} 21 | 22 | 23 | {{username}} 24 | {{fullname}} 25 | {{note}} 26 | 27 | {{location}} 28 | 29 | 30 | {{#updates}} 31 | 32 | {{id}} 33 | {{updated}} 34 | {{title}} 35 | {{content}} 36 | http://activitystrea.ms/schema/1.0/{{verb}} 37 | http://activitystrea.ms/schema/1.0/{{type}} 38 | 39 | {{/updates}} 40 | 41 | 42 | -------------------------------------------------------------------------------- /lib/ostatus/templates/user.html.mu: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Laurent (eschnou) - Identi.ca 5 | 6 | 7 | 8 | {{#updates}} 9 |

{{title}}

10 | {{updated}} 11 | {{/updates}} 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /lib/ostatus/templates/userXrd.xml.mu: -------------------------------------------------------------------------------- 1 | 2 | 3 | {{subject}} 4 | {{alias}} 5 | {{#links}} 6 | 7 | {{/links}} 8 | -------------------------------------------------------------------------------- /lib/ostatus/webfinger.js: -------------------------------------------------------------------------------- 1 | /* 2 | * node-ostatus - An implementation of OStatus for node.js 3 | * 4 | * Copyright (C) 2010 Laurent Eschenauer 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | 25 | var Sys = require('util') 26 | ,Url = require('url') 27 | ,Sax = require('sax') 28 | ,Path = require('path') 29 | ,Mu = require('mu') 30 | ,Http = require('./http.js'); 31 | 32 | /* 33 | * Generate the domain's host-meta XRD. 34 | * 35 | * This function simply process the hostXrd.xml.mu template and output the result. The template 36 | * is a fairly basic one with just a LRDD link indicating how to fetch a user XRD. 37 | * 38 | * @param {String} host 39 | * the domain name 40 | * @param {Function} callback 41 | * a callback function in the form function(err, result) 42 | * 43 | */ 44 | function hostXrd(host, callback) { 45 | var context = {host: host}; 46 | var buffer = ''; 47 | 48 | Mu.compile('hostXrd.xml.mu', function (err, parsed) { 49 | if (err) return callback(err); 50 | Mu.render(parsed, context) 51 | .on('data', function (c) { buffer += c.toString(); }) 52 | .on('end', function () { callback(null, buffer); }); 53 | }); 54 | } 55 | 56 | 57 | /* 58 | * Generate a user's XRD. 59 | * 60 | * This function simply process the userXrd.xml.mu template and fill-in the subject, alias and links. 61 | * 62 | * @param {String} subject 63 | * the value of the subject item. This should be the acct uri that is being served. 64 | * @param {String} alias 65 | * an alias field. Usually a HTTP URI identifying the same user resource. 66 | * @param {Array} links 67 | * an array of links to be added in the XRD. 68 | * @param {Function} callback 69 | * a callback function in the form function(err, result) 70 | * 71 | * A link has the form: 72 | * var link = { 73 | * "href": "..", 74 | * "rel" : "..", 75 | * "type": "..", 76 | * "ref" : ".. 77 | * } 78 | * 79 | * TODO Allow for more than one alias (array instead of single value) 80 | * 81 | */ 82 | function userXrd(subject, alias, links, callback) { 83 | var buffer = ''; 84 | var context = { 85 | subject: subject 86 | ,alias: alias 87 | ,links: links 88 | }; 89 | 90 | Mu.compile('userXrd.xml.mu', function (err, parsed) { 91 | if (err) return callback(err); 92 | Mu.render(parsed, context) 93 | .on('data', function (c) { buffer += c.toString(); }) 94 | .on('end', function () {callback(null, buffer);}); 95 | }); 96 | } 97 | 98 | /* 99 | * Perform a webfinger lookup for the provided acct: uri, the result is a JSON representation 100 | * of the user XRD. 101 | * 102 | * @param {String} identifier 103 | * a string representation of a valid acct: uri 104 | * @param {Function} callback 105 | * a callback function of the form function(err, result) 106 | * 107 | * The XRD object has the form: 108 | * var xrd = { 109 | * "subject": the subject of the xrd lookup 110 | * "alias" : an array of strings (a xrd can have more than one alias) 111 | * "links" : an array of links 112 | * } 113 | * 114 | * TODO Add support for HTTP URIs 115 | */ 116 | function lookup(identifier, callback) { 117 | var url = Url.parse(identifier); 118 | if (url.protocol != "acct:" && url.protocol != "http:") { 119 | callback(new Error("Protocol not supported")); 120 | return; 121 | } 122 | _fetchHostMeta(url.hostname, function (err, template) { 123 | if (err) return callback(err); 124 | _fetchUserMeta(template, identifier, function (err, meta) { 125 | if (err) return callback(err); 126 | return callback(null, meta); 127 | }); 128 | }); 129 | } 130 | 131 | /* 132 | * Parse a string representation of an acct uri and return the username and 133 | * hostname parts. The input can ommit the acct: prefix. 134 | * 135 | * @param {String} pAcct 136 | * a string representing an acct: uri (prefix can be omitted) 137 | * 138 | * @return 139 | * an object with a username and hostname property 140 | * 141 | * Example: 142 | * parseAcct("user@example.com") will return: 143 | * { "username": "user", "hostname": "example.com"} 144 | */ 145 | function parseAcct(pAcct) { 146 | // Add the acct prefix if required 147 | if (pAcct<5 || pAcct.substring(0,5) != "acct:") { 148 | pAcct = "acct:" + pAcct; 149 | } 150 | 151 | // Validate the user account 152 | var acct = Url.parse(pAcct); 153 | if (acct == undefined || acct.auth == undefined || acct.hostname == undefined) { 154 | return false; 155 | } 156 | 157 | // Return the parsed account 158 | var result = {}; 159 | result.hostname = acct.hostname; 160 | result.username = acct.auth; 161 | return result; 162 | } 163 | 164 | /////// Private helper functions 165 | 166 | /* 167 | * Get the host XRD. 168 | */ 169 | function _fetchHostMeta(host, callback) { 170 | var url = "http://" + host + "/.well-known/host-meta"; 171 | console.log("Requesting host meta at " + url); 172 | Http.get(url, function(err, response, content) { 173 | if (err) return callback(err); 174 | if (response.statusCode == 200) { 175 | try { 176 | callback(null, _parseHostMeta(content)); 177 | } catch (e) { 178 | callback(e); 179 | } 180 | } else { 181 | callback(new Error("Fetching host meta returned HTTP Status " + response.statusCode)); 182 | } 183 | }); 184 | } 185 | 186 | function _parseHostMeta(content) { 187 | var template; 188 | var parser = Sax.parser(); 189 | parser.onopentag = function(node) { 190 | /* descendant-or-self::node()[@name='link' and @rel='lrdd']/@template */ 191 | if (node.name === 'LINK' && 192 | node.attributes.rel === 'lrdd' && 193 | node.attributes.template) 194 | template = node.attributes.template; 195 | }; 196 | parser.write(content).close(); 197 | return template; 198 | } 199 | 200 | /* 201 | * Get a user LRDD from a trmplate URI and an identifier. 202 | */ 203 | function _fetchUserMeta(template, identifier, callback) { 204 | var url = template.replace("{uri}", identifier); 205 | console.log("Requesting user meta at " + url); 206 | Http.get(url, function(err, response, content) { 207 | if (err) return callback(err); 208 | if (response.statusCode == 200) { 209 | try { 210 | callback(null, _parseUserMeta(content)); 211 | } catch(e) { 212 | callback(e); 213 | } 214 | } else { 215 | callback(new Error("Fetching user meta returned HTTP Status " + response.statusCode)); 216 | } 217 | }); 218 | } 219 | 220 | /* 221 | * Parse a user LRDD into a JSON object. 222 | */ 223 | function _parseUserMeta(content) { 224 | var result = { 225 | "alias": [], 226 | "links": [] 227 | }; 228 | var parser = Sax.parser(), state; 229 | parser.onopentag = function(node) { 230 | switch(node.name) { 231 | case 'SUBJECT': 232 | state = 'subject'; 233 | break; 234 | case 'LINK': 235 | result.links.push(node.attributes); 236 | break; 237 | case 'ALIAS': 238 | state = 'alias'; 239 | break; 240 | } 241 | } 242 | /* TODO: for multiple invokations, and w/ cdata */ 243 | parser.ontext = function(s) { 244 | switch(state) { 245 | case 'subject': 246 | result.subject = s; 247 | break; 248 | case 'alias': 249 | result.alias.push(s); 250 | break; 251 | } 252 | }; 253 | parser.onclosetag = function() { 254 | state = null; 255 | }; 256 | parser.write(content).close(); 257 | 258 | return result; 259 | } 260 | 261 | exports.lookup = lookup; 262 | exports.hostXrd = hostXrd; 263 | exports.userXrd = userXrd; 264 | exports.parseAcct = parseAcct; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { "name" : "ostatus" 2 | , "description" : "An implementation of the OStatus protocol stack for nodejs." 3 | , "engines": { "node" : ">=0.4.0" } 4 | , "version": "v0.1.0" 5 | , "keywords": ["ostatus", "hcard", "pubsubhubbub", "atom", "activity", "salmon"] 6 | , "homepage": "http://github.com/eschnou/node-ostatus" 7 | , "author": "Laurent Eschenauer (http://eschnou.com)" 8 | , "bin" : { "status" : "./bin/status.js", "profile": "./bin/profile.js", "webfinger": "./bin/webfinger.js"} 9 | , "main" : "./lib/ostatus" 10 | , "repository" : { "type": "git", "url": "https://github.com/eschnou/node-ostatus.git"} 11 | , "dependencies" : { 12 | "mu" : "http://github.com/raycmorgan/Mu/tarball/v2" 13 | ,"sax" : ">=0.1.2" 14 | ,"flow" : "0.2.x" 15 | ,"base64" : "2.0.x" 16 | ,"bignumber" : "1.1.x" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /tests/test-as.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | var fs = require('fs'); 3 | var path = require('path'); 4 | var ostatus = require('ostatus'); 5 | var xml = fs.readFileSync(path.join(__dirname, 'test-as.xml'), 'utf-8'); 6 | 7 | function test_render() { 8 | ostatus.as.render("http://example.com", activities, profile, function(err, result) { 9 | assert.equal(result, xml, "Xml output did not match expected result."); 10 | }); 11 | } 12 | 13 | var activities = [ 14 | { 15 | id: "tag:eschnou@shoutr.org,2011-03-06:FKZbF1nx", 16 | title: "Hello, World", 17 | content: "Hello, World", 18 | verb: "post", 19 | type:"note", 20 | updated:"2011-03-06T15:02:56" 21 | }, 22 | { 23 | id: "tag:eschnou@shoutr.org,2011-03-07:FKdgfF1nx", 24 | title: "Another one for the road.", 25 | content: "Another one for the road.", 26 | verb: "post", 27 | type:"note", 28 | updated:"2011-03-07T09:02:56" 29 | } 30 | ]; 31 | 32 | var profile = { 33 | username: "eschnou", 34 | fullname: "Laurent Eschenauer", 35 | avatar: "http://shoutr.org/avatar/eschnou.jpg" 36 | }; 37 | 38 | test_render(); 39 | -------------------------------------------------------------------------------- /tests/test-as.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | http://http://example.com/updates/eschnou.atom 4 | Latest updates from Laurent Eschenauer 5 | 2011-03-06T15:02:56 6 | 7 | 8 | 9 | 10 | 11 | Laurent Eschenauer 12 | http://http://example.com/users/eschnou 13 | 14 | 15 | http://activitystrea.ms/schema/1.0/person 16 | http://http://example.com/users/eschnou 17 | Laurent Eschenauer 18 | 19 | 20 | eschnou 21 | Laurent Eschenauer 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | tag:eschnou@shoutr.org,2011-03-06:FKZbF1nx 30 | 2011-03-06T15:02:56 31 | Hello, World 32 | Hello, <b>World</b> 33 | http://activitystrea.ms/schema/1.0/post 34 | http://activitystrea.ms/schema/1.0/note 35 | 36 | 37 | 38 | tag:eschnou@shoutr.org,2011-03-07:FKdgfF1nx 39 | 2011-03-07T09:02:56 40 | Another one for the road. 41 | Another one for the road. 42 | http://activitystrea.ms/schema/1.0/post 43 | http://activitystrea.ms/schema/1.0/note 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /tests/test-minime.js: -------------------------------------------------------------------------------- 1 | var assert=require('assert'); 2 | var fs=require('fs'); 3 | var path=require('path'); 4 | var ostatus=require('ostatus'); 5 | 6 | /* 7 | * Test interoperability with a Status.net signed salmon magic enveloppe. 8 | */ 9 | 10 | function test(){ 11 | assert.ok(ostatus.salmon.verify_signature(me, key)); 12 | } 13 | 14 | var me = { 15 | sigs: [ 16 | { value: 'OvoBbss29_RYOzE3jHYBA3Aqae7UtekDO7W9QKUd6Smb9hccEgCWlCHoq1X7MW6smjod2aGrfiYrNO7wuj6-3igrBiKpWfNXDThjVatwEJ3FZKpaNlNhV9BOGry8tNU43VwQNq55qWYqgQSdNHA0R-jAtr2o3N-MsNsSSWXUDRg=' } 17 | ], 18 | data: 'PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPGVudHJ5IHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDA1L0F0b20iIHhtbG5zOmFjdGl2aXR5PSJodHRwOi8vYWN0aXZpdHlzdHJlYS5tcy9zcGVjLzEuMC8iIHhtbG5zOnBvY289Imh0dHA6Ly9wb3J0YWJsZWNvbnRhY3RzLm5ldC9zcGVjLzEuMCI-CiAgPGlkPmh0dHA6Ly93d3cubG9ic3Rlcm1vbnN0ZXIub3JnL3Byb2ZpbGUva29za2k_cG9zdGlkPTEzMDAyNjk5MDc8L2lkPgogIDx1cGRhdGVkPjIwMTEtMDMtMTZUMTE6MDU6MDcrMDE6MDA8L3VwZGF0ZWQ-CiAgPGxpbmsgcmVsPSJhbHRlcm5hdGUiIHR5cGU9InRleHQvaHRtbCIgaHJlZj0iaHR0cDovL3d3dy5sb2JzdGVybW9uc3Rlci5vcmcvcHJvZmlsZS9rb3NraT9wb3N0aWQ9MTMwMDI2OTkwNyIvPgogIDx0aXRsZT5IaSBFc2Nobm91LCBQSU5HIC4uLjwvdGl0bGU-CiAgPGNvbnRlbnQgdHlwZT0iaHRtbCI-SGkgRXNjaG5vdSwgUElORyAuLi48L2NvbnRlbnQ-CiAgPGF1dGhvcj4KICAgIDx1cmk-YWNjdDprb3NraUBsb2JzdGVybW9uc3Rlci5vcmc8L3VyaT4KICAgIDxuYW1lPlR1b21hcyBLb3NraTwvbmFtZT4KICAgIDxsaW5rIHJlbD0icGhvdG8iIHR5cGU9ImltYWdlL3BuZyIgaHJlZj0iaHR0cDovL3d3dy5ncmF2YXRhci5jb20vYXZhdGFyL2EwYzZlNjNiOWI4YjhkNGY2ZmFhM2M5ZWE2MmY0M2JmLnBuZyIvPgogICAgPGxpbmsgcmVsPSJhbHRlcm5hdGUiIHR5cGU9InRleHQvaHRtbCIgaHJlZj0iaHR0cDovL3d3dy5sb2JzdGVybW9uc3Rlci5vcmcvcHJvZmlsZS9rb3NraSIvPgogIDwvYXV0aG9yPgogIDxsaW5rIHJlbD0ib3N0YXR1czphdHRlbnRpb24iIHR5cGU9IiIgaHJlZj0iaHR0cDovL2VzY2hlbmF1ZXIuYmUvdXNlcnMvbGF1cmVudCIvPgogIDxsaW5rIHJlbD0ibWVudGlvbmVkIiB0eXBlPSIiIGhyZWY9Imh0dHA6Ly9lc2NoZW5hdWVyLmJlL3VzZXJzL2xhdXJlbnQiLz4KICA8YWN0aXZpdHk6YWN0b3I-CiAgICA8YWN0aXZpdHk6b2JqZWN0LXR5cGU-aHR0cDovL2FjdGl2aXR5c3RyZWEubXMvc2NoZW1hLzEuMC9wZXJzb248L2FjdGl2aXR5Om9iamVjdC10eXBlPgogICAgPGlkPmh0dHA6Ly93d3cubG9ic3Rlcm1vbnN0ZXIub3JnL3Byb2ZpbGUva29za2k8L2lkPgogICAgPHRpdGxlPlR1b21hcyBLb3NraTwvdGl0bGU-CiAgICA8bGluayByZWw9ImFsdGVybmF0ZSIgdHlwZT0idGV4dC9odG1sIiBocmVmPSJodHRwOi8vd3d3LmxvYnN0ZXJtb25zdGVyLm9yZy9wcm9maWxlL2tvc2tpIi8-CiAgICA8bGluayByZWw9ImF2YXRhciIgdHlwZT0iaW1hZ2UvcG5nIiBocmVmPSJodHRwOi8vd3d3LmdyYXZhdGFyLmNvbS9hdmF0YXIvYTBjNmU2M2I5YjhiOGQ0ZjZmYWEzYzllYTYyZjQzYmYucG5nIi8-CiAgICA8bGluayByZWw9InBob3RvIiB0eXBlPSJpbWFnZS9wbmciIGhyZWY9Imh0dHA6Ly93d3cuZ3JhdmF0YXIuY29tL2F2YXRhci9hMGM2ZTYzYjliOGI4ZDRmNmZhYTNjOWVhNjJmNDNiZi5wbmciLz4KICAgIDxwb2NvOm5hbWU-CiAgICAgIDxwb2NvOmdpdmVuTmFtZT5UdW9tYXM8L3BvY286Z2l2ZW5OYW1lPgogICAgICA8cG9jbzpmYW1pbHlOYW1lPktvc2tpPC9wb2NvOmZhbWlseU5hbWU-CiAgICA8L3BvY286bmFtZT4KICA8L2FjdGl2aXR5OmFjdG9yPgogIDxhY3Rpdml0eTp2ZXJiPmh0dHA6Ly9hY3Rpdml0eXN0cmVhLm1zL3NjaGVtYS8xLjAvcG9zdDwvYWN0aXZpdHk6dmVyYj4KICA8YWN0aXZpdHk6b2JqZWN0PgogICAgPGFjdGl2aXR5Om9iamVjdC10eXBlPmh0dHA6Ly9hY3Rpdml0eXN0cmVhLm1zL3NjaGVtYS8xLjAvbm90ZTwvYWN0aXZpdHk6b2JqZWN0LXR5cGU-CiAgICA8aWQ-aHR0cDovL3d3dy5sb2JzdGVybW9uc3Rlci5vcmcvcHJvZmlsZS9rb3NraT9wb3N0aWQ9MTMwMDI2OTkwNzwvaWQ-CiAgICA8Y29udGVudCB0eXBlPSJodG1sIj5IaSBFc2Nobm91LCBQSU5HIC4uLjwvY29udGVudD4KICAgIDxsaW5rIHJlbD0iYWx0ZXJuYXRlIiB0eXBlPSJ0ZXh0L2h0bWwiIGhyZWY9Imh0dHA6Ly93d3cubG9ic3Rlcm1vbnN0ZXIub3JnL3Byb2ZpbGUva29za2k_cG9zdGlkPTEzMDAyNjk5MDciLz4KICA8L2FjdGl2aXR5Om9iamVjdD4KPC9lbnRyeT4K', 19 | data_type: 'application/atom xml', 20 | encoding: 'base64url', 21 | alg: 'RSA-SHA256' 22 | }; 23 | 24 | var key = "RSA.quCNBj3KbWmJG1huVxTvHWjCenThHYSb49y7HLPz_fVUfTUYMVfz7Qt8IkTXKj9TartEhNG2FzTIZzu4mkSzkKDZ9NflWs2VIJCWZoF-xJY4FAGKvja-Tuxn-K2trjKa6bypIEfM4qYWVHr_Sxfx3r4fioAe2z90p3AKF6aWm10=.AQAB"; 25 | 26 | 27 | test(); 28 | -------------------------------------------------------------------------------- /tests/test-openssl.js: -------------------------------------------------------------------------------- 1 | var assert=require('assert'); 2 | var fs=require('fs'); 3 | var path=require('path'); 4 | var ostatus=require('ostatus'); 5 | 6 | /* 7 | * Test interoperability with an OpenSSL signed salmon magic enveloppe. 8 | */ 9 | 10 | function test_openssl(){ 11 | assert.ok(ostatus.salmon.verify_signature(me, key)); 12 | } 13 | 14 | var me = { 15 | sigs: [ 16 | { value: "b_C8bzd9iF31lNFCkDvstOalH2mQVCFU5O04OC1xqoVTCpvQtt2V1JE-0cFKZ7XYErug0x5pz51dDg2ctVVIgK6mJmitF55-0RQNwtsVjCDsjKnd8s7ZmnGOYWWCGPgCQJ011AgAWnxacpCwb9W38BvJxKtwaOM0WSac9vgxY8g" } 17 | ], 18 | data: 'QWxsIHlvdXJzIGJhc2VzIGFyZSBiZWxvbmcgdG8gdXMuCg', 19 | data_type: 'application/atom+xml', 20 | encoding: 'base64url', 21 | alg: 'RSA-SHA256' 22 | }; 23 | 24 | var key = "RSA.2YmPB7i6h_eJbkXWV8qaEfcI-V0JwQcj73ncG6KJx1TFPYxooYcMKGgK0IDV_em2KV4WEJu9HuedUyJkSDWHwSj18UvNfZ6Pue2uc6vFDPO8mN0q56PShGagdg4XdOxCXlUv2iAp7-malaJaIlLHyjhvxoVtD3itkXe2cgCed7c.AQAB"; 25 | 26 | 27 | test_openssl(); -------------------------------------------------------------------------------- /tests/test-salmon.input: -------------------------------------------------------------------------------- 1 | 2 | PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiID8-PGVudHJ5IHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDA1L0F0b20iIHhtbG5zOnRocj0iaHR0cDovL3B1cmwub3JnL3N5bmRpY2F0aW9uL3RocmVhZC8xLjAiIHhtbG5zOmFjdGl2aXR5PSJodHRwOi8vYWN0aXZpdHlzdHJlYS5tcy9zcGVjLzEuMC8iIHhtbG5zOmdlb3Jzcz0iaHR0cDovL3d3dy5nZW9yc3Mub3JnL2dlb3JzcyIgeG1sbnM6b3N0YXR1cz0iaHR0cDovL29zdGF0dXMub3JnL3NjaGVtYS8xLjAiIHhtbG5zOnBvY289Imh0dHA6Ly9wb3J0YWJsZWNvbnRhY3RzLm5ldC9zcGVjLzEuMCIgeG1sbnM6bWVkaWE9Imh0dHA6Ly9wdXJsLm9yZy9zeW5kaWNhdGlvbi9hdG9tbWVkaWEiIHhtbG5zOnN0YXR1c25ldD0iaHR0cDovL3N0YXR1cy5uZXQvc2NoZW1hL2FwaS8xLyI-CiA8YWN0aXZpdHk6b2JqZWN0LXR5cGU-aHR0cDovL2FjdGl2aXR5c3RyZWEubXMvc2NoZW1hLzEuMC9ub3RlPC9hY3Rpdml0eTpvYmplY3QtdHlwZT4KIDxpZD5odHRwOi8vaWRlbnRpLmNhL25vdGljZS82NTEzOTc5MjwvaWQ-CiA8dGl0bGU-dGhpcyBvbmUgaXMgZm9yIEBjYXBhQGVzY2hlbmF1ZXIuYmUgLSBlbmpveSAhPC90aXRsZT4KIDxjb250ZW50IHR5cGU9Imh0bWwiPnRoaXMgb25lIGlzIGZvciBAJmx0O3NwYW4gY2xhc3M9JnF1b3Q7dmNhcmQmcXVvdDsmZ3Q7Jmx0O2EgaHJlZj0mcXVvdDtodHRwOi8vZXNjaGVuYXVlci5iZS91c2Vycy9jYXBhJnF1b3Q7IGNsYXNzPSZxdW90O3VybCZxdW90OyZndDsmbHQ7c3BhbiBjbGFzcz0mcXVvdDtmbiBuaWNrbmFtZSZxdW90OyZndDtjYXBhQGVzY2hlbmF1ZXIuYmUmbHQ7L3NwYW4mZ3Q7Jmx0Oy9hJmd0OyZsdDsvc3BhbiZndDsgLSBlbmpveSAhPC9jb250ZW50PgogPGxpbmsgcmVsPSJhbHRlcm5hdGUiIHR5cGU9InRleHQvaHRtbCIgaHJlZj0iaHR0cDovL2lkZW50aS5jYS9ub3RpY2UvNjUxMzk3OTIiLz4KIDxhY3Rpdml0eTp2ZXJiPmh0dHA6Ly9hY3Rpdml0eXN0cmVhLm1zL3NjaGVtYS8xLjAvcG9zdDwvYWN0aXZpdHk6dmVyYj4KIDxwdWJsaXNoZWQ-MjAxMS0wMi0yMlQyMToyMjo0OSswMDowMDwvcHVibGlzaGVkPgogPHVwZGF0ZWQ-MjAxMS0wMi0yMlQyMToyMjo0OSswMDowMDwvdXBkYXRlZD4KIDxhdXRob3I-CiAgPGFjdGl2aXR5Om9iamVjdC10eXBlPmh0dHA6Ly9hY3Rpdml0eXN0cmVhLm1zL3NjaGVtYS8xLjAvcGVyc29uPC9hY3Rpdml0eTpvYmplY3QtdHlwZT4KICA8dXJpPmh0dHA6Ly9pZGVudGkuY2EvdXNlci8zODUyMTY8L3VyaT4KICA8bmFtZT5zaG91dHI8L25hbWU-CiAgPGxpbmsgcmVsPSJhbHRlcm5hdGUiIHR5cGU9InRleHQvaHRtbCIgaHJlZj0iaHR0cDovL2lkZW50aS5jYS9zaG91dHIiLz4KICA8bGluayByZWw9ImF2YXRhciIgdHlwZT0iaW1hZ2UvcG5nIiBtZWRpYTp3aWR0aD0iOTYiIG1lZGlhOmhlaWdodD0iOTYiIGhyZWY9Imh0dHA6Ly90aGVtZS5pZGVudGkuY2EvMC45LjdiZXRhMi9pZGVudGljYS9kZWZhdWx0LWF2YXRhci1wcm9maWxlLnBuZyIvPgogIDxsaW5rIHJlbD0iYXZhdGFyIiB0eXBlPSJpbWFnZS9wbmciIG1lZGlhOndpZHRoPSI0OCIgbWVkaWE6aGVpZ2h0PSI0OCIgaHJlZj0iaHR0cDovL3RoZW1lLmlkZW50aS5jYS8wLjkuN2JldGEyL2lkZW50aWNhL2RlZmF1bHQtYXZhdGFyLXN0cmVhbS5wbmciLz4KICA8bGluayByZWw9ImF2YXRhciIgdHlwZT0iaW1hZ2UvcG5nIiBtZWRpYTp3aWR0aD0iMjQiIG1lZGlhOmhlaWdodD0iMjQiIGhyZWY9Imh0dHA6Ly90aGVtZS5pZGVudGkuY2EvMC45LjdiZXRhMi9pZGVudGljYS9kZWZhdWx0LWF2YXRhci1taW5pLnBuZyIvPgogIDxwb2NvOnByZWZlcnJlZFVzZXJuYW1lPnNob3V0cjwvcG9jbzpwcmVmZXJyZWRVc2VybmFtZT4KICA8cG9jbzpkaXNwbGF5TmFtZT5TaG91dHI8L3BvY286ZGlzcGxheU5hbWU-CiAgPHBvY286dXJscz4KICAgPHBvY286dHlwZT5ob21lcGFnZTwvcG9jbzp0eXBlPgogICA8cG9jbzp2YWx1ZT5odHRwOi8vc2hvdXRyLm9yZzwvcG9jbzp2YWx1ZT4KICAgPHBvY286cHJpbWFyeT50cnVlPC9wb2NvOnByaW1hcnk-CjwvcG9jbzp1cmxzPgo8L2F1dGhvcj4KIDwhLS1EZXByZWNhdGlvbiB3YXJuaW5nOiBhY3Rpdml0eTphY3RvciBpcyBwcmVzZW50IG9ubHkgZm9yIGJhY2t3YXJkIGNvbXBhdGliaWxpdHkuIEl0IHdpbGwgYmUgcmVtb3ZlZCBpbiB0aGUgbmV4dCB2ZXJzaW9uIG9mIFN0YXR1c05ldC4tLT4KIDxhY3Rpdml0eTphY3Rvcj4KICA8YWN0aXZpdHk6b2JqZWN0LXR5cGU-aHR0cDovL2FjdGl2aXR5c3RyZWEubXMvc2NoZW1hLzEuMC9wZXJzb248L2FjdGl2aXR5Om9iamVjdC10eXBlPgogIDxpZD5odHRwOi8vaWRlbnRpLmNhL3VzZXIvMzg1MjE2PC9pZD4KICA8dGl0bGU-U2hvdXRyPC90aXRsZT4KICA8bGluayByZWw9ImFsdGVybmF0ZSIgdHlwZT0idGV4dC9odG1sIiBocmVmPSJodHRwOi8vaWRlbnRpLmNhL3Nob3V0ciIvPgogIDxsaW5rIHJlbD0iYXZhdGFyIiB0eXBlPSJpbWFnZS9wbmciIG1lZGlhOndpZHRoPSI5NiIgbWVkaWE6aGVpZ2h0PSI5NiIgaHJlZj0iaHR0cDovL3RoZW1lLmlkZW50aS5jYS8wLjkuN2JldGEyL2lkZW50aWNhL2RlZmF1bHQtYXZhdGFyLXByb2ZpbGUucG5nIi8-CiAgPGxpbmsgcmVsPSJhdmF0YXIiIHR5cGU9ImltYWdlL3BuZyIgbWVkaWE6d2lkdGg9IjQ4IiBtZWRpYTpoZWlnaHQ9IjQ4IiBocmVmPSJodHRwOi8vdGhlbWUuaWRlbnRpLmNhLzAuOS43YmV0YTIvaWRlbnRpY2EvZGVmYXVsdC1hdmF0YXItc3RyZWFtLnBuZyIvPgogIDxsaW5rIHJlbD0iYXZhdGFyIiB0eXBlPSJpbWFnZS9wbmciIG1lZGlhOndpZHRoPSIyNCIgbWVkaWE6aGVpZ2h0PSIyNCIgaHJlZj0iaHR0cDovL3RoZW1lLmlkZW50aS5jYS8wLjkuN2JldGEyL2lkZW50aWNhL2RlZmF1bHQtYXZhdGFyLW1pbmkucG5nIi8-CiAgPHBvY286cHJlZmVycmVkVXNlcm5hbWU-c2hvdXRyPC9wb2NvOnByZWZlcnJlZFVzZXJuYW1lPgogIDxwb2NvOmRpc3BsYXlOYW1lPlNob3V0cjwvcG9jbzpkaXNwbGF5TmFtZT4KICA8cG9jbzp1cmxzPgogICA8cG9jbzp0eXBlPmhvbWVwYWdlPC9wb2NvOnR5cGU-CiAgIDxwb2NvOnZhbHVlPmh0dHA6Ly9zaG91dHIub3JnPC9wb2NvOnZhbHVlPgogICA8cG9jbzpwcmltYXJ5PnRydWU8L3BvY286cHJpbWFyeT4KPC9wb2NvOnVybHM-CjwvYWN0aXZpdHk6YWN0b3I-CiA8bGluayByZWw9Im9zdGF0dXM6Y29udmVyc2F0aW9uIiBocmVmPSJodHRwOi8vaWRlbnRpLmNhL2NvbnZlcnNhdGlvbi82NDM4Mjk1NSIvPgogPGxpbmsgcmVsPSJvc3RhdHVzOmF0dGVudGlvbiIgaHJlZj0iaHR0cDovL2VzY2hlbmF1ZXIuYmUvdXNlcnMvY2FwYSIvPgogPGxpbmsgcmVsPSJtZW50aW9uZWQiIGhyZWY9Imh0dHA6Ly9lc2NoZW5hdWVyLmJlL3VzZXJzL2NhcGEiLz4KIDxnZW9yc3M6cG9pbnQ-NTAuNTY2NjcgNS41ODMzMzwvZ2VvcnNzOnBvaW50PgogPHNvdXJjZT4KICA8aWQ-aHR0cDovL2VzY2hlbmF1ZXIuYmUvdXBkYXRlcy9jYXBhLmF0b208L2lkPgogIDx0aXRsZT5jYXBhPC90aXRsZT4KICA8bGluayByZWw9ImFsdGVybmF0ZSIgdHlwZT0idGV4dC9odG1sIiBocmVmPSJodHRwOi8vZXNjaGVuYXVlci5iZS91c2Vycy9jYXBhIi8-CiAgPGxpbmsgcmVsPSJzZWxmIiB0eXBlPSJhcHBsaWNhdGlvbi9hdG9tK3htbCIgaHJlZj0iaHR0cDovL2VzY2hlbmF1ZXIuYmUvdXBkYXRlcy9jYXBhLmF0b20iLz4KICA8aWNvbj5odHRwOi8vdGhlbWUuaWRlbnRpLmNhLzAuOS43YmV0YTIvaWRlbnRpY2EvZGVmYXVsdC1hdmF0YXItcHJvZmlsZS5wbmc8L2ljb24-Cjwvc291cmNlPgogPGxpbmsgcmVsPSJzZWxmIiB0eXBlPSJhcHBsaWNhdGlvbi9hdG9tK3htbCIgaHJlZj0iaHR0cDovL2lkZW50aS5jYS9hcGkvc3RhdHVzZXMvc2hvdy82NTEzOTc5Mi5hdG9tIi8-CiA8bGluayByZWw9ImVkaXQiIHR5cGU9ImFwcGxpY2F0aW9uL2F0b20reG1sIiBocmVmPSJodHRwOi8vaWRlbnRpLmNhL2FwaS9zdGF0dXNlcy9zaG93LzY1MTM5NzkyLmF0b20iLz4KIDxzdGF0dXNuZXQ6bm90aWNlX2luZm8gbG9jYWxfaWQ9IjY1MTM5NzkyIiBzb3VyY2U9IndlYiI-PC9zdGF0dXNuZXQ6bm90aWNlX2luZm8-CjwvZW50cnk-Cg==base64urlRSA-SHA256UqKwh0XSOhdSD7U9nVHxB67sCNt8lQzkl5aPELQTfuhrlBoktbExhhkP4QGFg0WS0FgPnQpG24z5S4XIk2BTjI8My-VlwRWdeU72NtnLhZjz8EzA1aJTI_Drs71-YICuM_dLAJgo55pF4nIMkRN9KA-rS-y7oC3cwt01MknR8UQ= 3 | 4 | -------------------------------------------------------------------------------- /tests/test-salmon.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | var fs = require('fs'); 3 | var path = require('path'); 4 | var ostatus = require('ostatus'); 5 | var input = fs.readFileSync(path.join(__dirname, 'test-salmon.input'), 'utf-8'); 6 | var output = fs.readFileSync(path.join(__dirname, 'test-salmon.output'), 'utf-8'); 7 | 8 | function test_unpack() { 9 | ostatus.salmon.unpack(input, function(err, result) { 10 | if (err) throw err; 11 | assert.equal(result.data, expected.data, "Salmon did not properly unpack 'data'"); 12 | assert.equal(result.encoding, expected.encoding, "Salmon did not properly unpack 'encoding'"); 13 | assert.equal(result.alg, expected.alg, "Salmon did not properly unpack 'alg'"); 14 | assert.equal(result.sigs[0].value, expected.sigs[0].value, "Salmon did not properly unpack 'sig'"); 15 | assert.equal(result.data_type, expected.data_type, "Salmon did not properly unpack 'data_type'"); 16 | }); 17 | } 18 | 19 | function test_decode() { 20 | var decoded = ostatus.salmon.base64url_decode(expected.data); 21 | assert.equal(decoded, output, "Base64 decoding did not provide expected result"); 22 | } 23 | 24 | function test_encode() { 25 | var encoded = ostatus.salmon.base64url_encode(output); 26 | assert.equal(encoded, expected.data, "Based64 encoding did not provide expected result"); 27 | } 28 | 29 | function test_verify() { 30 | ostatus.salmon.verify_signature(expected, key); 31 | } 32 | 33 | var expected = { 34 | sigs: [ 35 | { value: 'UqKwh0XSOhdSD7U9nVHxB67sCNt8lQzkl5aPELQTfuhrlBoktbExhhkP4QGFg0WS0FgPnQpG24z5S4XIk2BTjI8My-VlwRWdeU72NtnLhZjz8EzA1aJTI_Drs71-YICuM_dLAJgo55pF4nIMkRN9KA-rS-y7oC3cwt01MknR8UQ=' } 36 | ], 37 | data: 'PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiID8-PGVudHJ5IHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDA1L0F0b20iIHhtbG5zOnRocj0iaHR0cDovL3B1cmwub3JnL3N5bmRpY2F0aW9uL3RocmVhZC8xLjAiIHhtbG5zOmFjdGl2aXR5PSJodHRwOi8vYWN0aXZpdHlzdHJlYS5tcy9zcGVjLzEuMC8iIHhtbG5zOmdlb3Jzcz0iaHR0cDovL3d3dy5nZW9yc3Mub3JnL2dlb3JzcyIgeG1sbnM6b3N0YXR1cz0iaHR0cDovL29zdGF0dXMub3JnL3NjaGVtYS8xLjAiIHhtbG5zOnBvY289Imh0dHA6Ly9wb3J0YWJsZWNvbnRhY3RzLm5ldC9zcGVjLzEuMCIgeG1sbnM6bWVkaWE9Imh0dHA6Ly9wdXJsLm9yZy9zeW5kaWNhdGlvbi9hdG9tbWVkaWEiIHhtbG5zOnN0YXR1c25ldD0iaHR0cDovL3N0YXR1cy5uZXQvc2NoZW1hL2FwaS8xLyI-CiA8YWN0aXZpdHk6b2JqZWN0LXR5cGU-aHR0cDovL2FjdGl2aXR5c3RyZWEubXMvc2NoZW1hLzEuMC9ub3RlPC9hY3Rpdml0eTpvYmplY3QtdHlwZT4KIDxpZD5odHRwOi8vaWRlbnRpLmNhL25vdGljZS82NTEzOTc5MjwvaWQ-CiA8dGl0bGU-dGhpcyBvbmUgaXMgZm9yIEBjYXBhQGVzY2hlbmF1ZXIuYmUgLSBlbmpveSAhPC90aXRsZT4KIDxjb250ZW50IHR5cGU9Imh0bWwiPnRoaXMgb25lIGlzIGZvciBAJmx0O3NwYW4gY2xhc3M9JnF1b3Q7dmNhcmQmcXVvdDsmZ3Q7Jmx0O2EgaHJlZj0mcXVvdDtodHRwOi8vZXNjaGVuYXVlci5iZS91c2Vycy9jYXBhJnF1b3Q7IGNsYXNzPSZxdW90O3VybCZxdW90OyZndDsmbHQ7c3BhbiBjbGFzcz0mcXVvdDtmbiBuaWNrbmFtZSZxdW90OyZndDtjYXBhQGVzY2hlbmF1ZXIuYmUmbHQ7L3NwYW4mZ3Q7Jmx0Oy9hJmd0OyZsdDsvc3BhbiZndDsgLSBlbmpveSAhPC9jb250ZW50PgogPGxpbmsgcmVsPSJhbHRlcm5hdGUiIHR5cGU9InRleHQvaHRtbCIgaHJlZj0iaHR0cDovL2lkZW50aS5jYS9ub3RpY2UvNjUxMzk3OTIiLz4KIDxhY3Rpdml0eTp2ZXJiPmh0dHA6Ly9hY3Rpdml0eXN0cmVhLm1zL3NjaGVtYS8xLjAvcG9zdDwvYWN0aXZpdHk6dmVyYj4KIDxwdWJsaXNoZWQ-MjAxMS0wMi0yMlQyMToyMjo0OSswMDowMDwvcHVibGlzaGVkPgogPHVwZGF0ZWQ-MjAxMS0wMi0yMlQyMToyMjo0OSswMDowMDwvdXBkYXRlZD4KIDxhdXRob3I-CiAgPGFjdGl2aXR5Om9iamVjdC10eXBlPmh0dHA6Ly9hY3Rpdml0eXN0cmVhLm1zL3NjaGVtYS8xLjAvcGVyc29uPC9hY3Rpdml0eTpvYmplY3QtdHlwZT4KICA8dXJpPmh0dHA6Ly9pZGVudGkuY2EvdXNlci8zODUyMTY8L3VyaT4KICA8bmFtZT5zaG91dHI8L25hbWU-CiAgPGxpbmsgcmVsPSJhbHRlcm5hdGUiIHR5cGU9InRleHQvaHRtbCIgaHJlZj0iaHR0cDovL2lkZW50aS5jYS9zaG91dHIiLz4KICA8bGluayByZWw9ImF2YXRhciIgdHlwZT0iaW1hZ2UvcG5nIiBtZWRpYTp3aWR0aD0iOTYiIG1lZGlhOmhlaWdodD0iOTYiIGhyZWY9Imh0dHA6Ly90aGVtZS5pZGVudGkuY2EvMC45LjdiZXRhMi9pZGVudGljYS9kZWZhdWx0LWF2YXRhci1wcm9maWxlLnBuZyIvPgogIDxsaW5rIHJlbD0iYXZhdGFyIiB0eXBlPSJpbWFnZS9wbmciIG1lZGlhOndpZHRoPSI0OCIgbWVkaWE6aGVpZ2h0PSI0OCIgaHJlZj0iaHR0cDovL3RoZW1lLmlkZW50aS5jYS8wLjkuN2JldGEyL2lkZW50aWNhL2RlZmF1bHQtYXZhdGFyLXN0cmVhbS5wbmciLz4KICA8bGluayByZWw9ImF2YXRhciIgdHlwZT0iaW1hZ2UvcG5nIiBtZWRpYTp3aWR0aD0iMjQiIG1lZGlhOmhlaWdodD0iMjQiIGhyZWY9Imh0dHA6Ly90aGVtZS5pZGVudGkuY2EvMC45LjdiZXRhMi9pZGVudGljYS9kZWZhdWx0LWF2YXRhci1taW5pLnBuZyIvPgogIDxwb2NvOnByZWZlcnJlZFVzZXJuYW1lPnNob3V0cjwvcG9jbzpwcmVmZXJyZWRVc2VybmFtZT4KICA8cG9jbzpkaXNwbGF5TmFtZT5TaG91dHI8L3BvY286ZGlzcGxheU5hbWU-CiAgPHBvY286dXJscz4KICAgPHBvY286dHlwZT5ob21lcGFnZTwvcG9jbzp0eXBlPgogICA8cG9jbzp2YWx1ZT5odHRwOi8vc2hvdXRyLm9yZzwvcG9jbzp2YWx1ZT4KICAgPHBvY286cHJpbWFyeT50cnVlPC9wb2NvOnByaW1hcnk-CjwvcG9jbzp1cmxzPgo8L2F1dGhvcj4KIDwhLS1EZXByZWNhdGlvbiB3YXJuaW5nOiBhY3Rpdml0eTphY3RvciBpcyBwcmVzZW50IG9ubHkgZm9yIGJhY2t3YXJkIGNvbXBhdGliaWxpdHkuIEl0IHdpbGwgYmUgcmVtb3ZlZCBpbiB0aGUgbmV4dCB2ZXJzaW9uIG9mIFN0YXR1c05ldC4tLT4KIDxhY3Rpdml0eTphY3Rvcj4KICA8YWN0aXZpdHk6b2JqZWN0LXR5cGU-aHR0cDovL2FjdGl2aXR5c3RyZWEubXMvc2NoZW1hLzEuMC9wZXJzb248L2FjdGl2aXR5Om9iamVjdC10eXBlPgogIDxpZD5odHRwOi8vaWRlbnRpLmNhL3VzZXIvMzg1MjE2PC9pZD4KICA8dGl0bGU-U2hvdXRyPC90aXRsZT4KICA8bGluayByZWw9ImFsdGVybmF0ZSIgdHlwZT0idGV4dC9odG1sIiBocmVmPSJodHRwOi8vaWRlbnRpLmNhL3Nob3V0ciIvPgogIDxsaW5rIHJlbD0iYXZhdGFyIiB0eXBlPSJpbWFnZS9wbmciIG1lZGlhOndpZHRoPSI5NiIgbWVkaWE6aGVpZ2h0PSI5NiIgaHJlZj0iaHR0cDovL3RoZW1lLmlkZW50aS5jYS8wLjkuN2JldGEyL2lkZW50aWNhL2RlZmF1bHQtYXZhdGFyLXByb2ZpbGUucG5nIi8-CiAgPGxpbmsgcmVsPSJhdmF0YXIiIHR5cGU9ImltYWdlL3BuZyIgbWVkaWE6d2lkdGg9IjQ4IiBtZWRpYTpoZWlnaHQ9IjQ4IiBocmVmPSJodHRwOi8vdGhlbWUuaWRlbnRpLmNhLzAuOS43YmV0YTIvaWRlbnRpY2EvZGVmYXVsdC1hdmF0YXItc3RyZWFtLnBuZyIvPgogIDxsaW5rIHJlbD0iYXZhdGFyIiB0eXBlPSJpbWFnZS9wbmciIG1lZGlhOndpZHRoPSIyNCIgbWVkaWE6aGVpZ2h0PSIyNCIgaHJlZj0iaHR0cDovL3RoZW1lLmlkZW50aS5jYS8wLjkuN2JldGEyL2lkZW50aWNhL2RlZmF1bHQtYXZhdGFyLW1pbmkucG5nIi8-CiAgPHBvY286cHJlZmVycmVkVXNlcm5hbWU-c2hvdXRyPC9wb2NvOnByZWZlcnJlZFVzZXJuYW1lPgogIDxwb2NvOmRpc3BsYXlOYW1lPlNob3V0cjwvcG9jbzpkaXNwbGF5TmFtZT4KICA8cG9jbzp1cmxzPgogICA8cG9jbzp0eXBlPmhvbWVwYWdlPC9wb2NvOnR5cGU-CiAgIDxwb2NvOnZhbHVlPmh0dHA6Ly9zaG91dHIub3JnPC9wb2NvOnZhbHVlPgogICA8cG9jbzpwcmltYXJ5PnRydWU8L3BvY286cHJpbWFyeT4KPC9wb2NvOnVybHM-CjwvYWN0aXZpdHk6YWN0b3I-CiA8bGluayByZWw9Im9zdGF0dXM6Y29udmVyc2F0aW9uIiBocmVmPSJodHRwOi8vaWRlbnRpLmNhL2NvbnZlcnNhdGlvbi82NDM4Mjk1NSIvPgogPGxpbmsgcmVsPSJvc3RhdHVzOmF0dGVudGlvbiIgaHJlZj0iaHR0cDovL2VzY2hlbmF1ZXIuYmUvdXNlcnMvY2FwYSIvPgogPGxpbmsgcmVsPSJtZW50aW9uZWQiIGhyZWY9Imh0dHA6Ly9lc2NoZW5hdWVyLmJlL3VzZXJzL2NhcGEiLz4KIDxnZW9yc3M6cG9pbnQ-NTAuNTY2NjcgNS41ODMzMzwvZ2VvcnNzOnBvaW50PgogPHNvdXJjZT4KICA8aWQ-aHR0cDovL2VzY2hlbmF1ZXIuYmUvdXBkYXRlcy9jYXBhLmF0b208L2lkPgogIDx0aXRsZT5jYXBhPC90aXRsZT4KICA8bGluayByZWw9ImFsdGVybmF0ZSIgdHlwZT0idGV4dC9odG1sIiBocmVmPSJodHRwOi8vZXNjaGVuYXVlci5iZS91c2Vycy9jYXBhIi8-CiAgPGxpbmsgcmVsPSJzZWxmIiB0eXBlPSJhcHBsaWNhdGlvbi9hdG9tK3htbCIgaHJlZj0iaHR0cDovL2VzY2hlbmF1ZXIuYmUvdXBkYXRlcy9jYXBhLmF0b20iLz4KICA8aWNvbj5odHRwOi8vdGhlbWUuaWRlbnRpLmNhLzAuOS43YmV0YTIvaWRlbnRpY2EvZGVmYXVsdC1hdmF0YXItcHJvZmlsZS5wbmc8L2ljb24-Cjwvc291cmNlPgogPGxpbmsgcmVsPSJzZWxmIiB0eXBlPSJhcHBsaWNhdGlvbi9hdG9tK3htbCIgaHJlZj0iaHR0cDovL2lkZW50aS5jYS9hcGkvc3RhdHVzZXMvc2hvdy82NTEzOTc5Mi5hdG9tIi8-CiA8bGluayByZWw9ImVkaXQiIHR5cGU9ImFwcGxpY2F0aW9uL2F0b20reG1sIiBocmVmPSJodHRwOi8vaWRlbnRpLmNhL2FwaS9zdGF0dXNlcy9zaG93LzY1MTM5NzkyLmF0b20iLz4KIDxzdGF0dXNuZXQ6bm90aWNlX2luZm8gbG9jYWxfaWQ9IjY1MTM5NzkyIiBzb3VyY2U9IndlYiI-PC9zdGF0dXNuZXQ6bm90aWNlX2luZm8-CjwvZW50cnk-Cg==', 38 | data_type: 'application/atom xml', 39 | encoding: 'base64url', 40 | alg: 'RSA-SHA256' 41 | }; 42 | 43 | var key = "RSA.wEcnhyKFkapxOf7ycAednRNk3zrt9Y4XUjZ2UAR00BcVmI3jhS0GehRzZhphMgVLsRQhKQ-GR11A1U1HtWQTlfghGUCHK9WBBheTOr3Fl-53lXqaWLefofaOk-WFa-KTw7Ke57EsOHG1fB2lHhhisWel646mmbn3B3WyYkEnE3s=.AQAB"; 44 | 45 | test_unpack(); 46 | test_decode(); 47 | test_encode(); 48 | test_verify(); -------------------------------------------------------------------------------- /tests/test-salmon.output: -------------------------------------------------------------------------------- 1 | 2 | http://activitystrea.ms/schema/1.0/note 3 | http://identi.ca/notice/65139792 4 | this one is for @capa@eschenauer.be - enjoy ! 5 | this one is for @<span class="vcard"><a href="http://eschenauer.be/users/capa" class="url"><span class="fn nickname">capa@eschenauer.be</span></a></span> - enjoy ! 6 | 7 | http://activitystrea.ms/schema/1.0/post 8 | 2011-02-22T21:22:49+00:00 9 | 2011-02-22T21:22:49+00:00 10 | 11 | http://activitystrea.ms/schema/1.0/person 12 | http://identi.ca/user/385216 13 | shoutr 14 | 15 | 16 | 17 | 18 | shoutr 19 | Shoutr 20 | 21 | homepage 22 | http://shoutr.org 23 | true 24 | 25 | 26 | 27 | 28 | http://activitystrea.ms/schema/1.0/person 29 | http://identi.ca/user/385216 30 | Shoutr 31 | 32 | 33 | 34 | 35 | shoutr 36 | Shoutr 37 | 38 | homepage 39 | http://shoutr.org 40 | true 41 | 42 | 43 | 44 | 45 | 46 | 50.56667 5.58333 47 | 48 | http://eschenauer.be/updates/capa.atom 49 | capa 50 | 51 | 52 | http://theme.identi.ca/0.9.7beta2/identica/default-avatar-profile.png 53 | 54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /tests/test-statusnet.js: -------------------------------------------------------------------------------- 1 | var assert=require('assert'); 2 | var fs=require('fs'); 3 | var path=require('path'); 4 | var ostatus=require('ostatus'); 5 | 6 | /* 7 | * Test interoperability with a Status.net signed salmon magic enveloppe. 8 | */ 9 | 10 | function test(){ 11 | assert.ok(ostatus.salmon.verify_signature(me, key)); 12 | } 13 | 14 | var me = { 15 | sigs: [ 16 | { value: 'UqKwh0XSOhdSD7U9nVHxB67sCNt8lQzkl5aPELQTfuhrlBoktbExhhkP4QGFg0WS0FgPnQpG24z5S4XIk2BTjI8My-VlwRWdeU72NtnLhZjz8EzA1aJTI_Drs71-YICuM_dLAJgo55pF4nIMkRN9KA-rS-y7oC3cwt01MknR8UQ=' } 17 | ], 18 | data: 'PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiID8-PGVudHJ5IHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDA1L0F0b20iIHhtbG5zOnRocj0iaHR0cDovL3B1cmwub3JnL3N5bmRpY2F0aW9uL3RocmVhZC8xLjAiIHhtbG5zOmFjdGl2aXR5PSJodHRwOi8vYWN0aXZpdHlzdHJlYS5tcy9zcGVjLzEuMC8iIHhtbG5zOmdlb3Jzcz0iaHR0cDovL3d3dy5nZW9yc3Mub3JnL2dlb3JzcyIgeG1sbnM6b3N0YXR1cz0iaHR0cDovL29zdGF0dXMub3JnL3NjaGVtYS8xLjAiIHhtbG5zOnBvY289Imh0dHA6Ly9wb3J0YWJsZWNvbnRhY3RzLm5ldC9zcGVjLzEuMCIgeG1sbnM6bWVkaWE9Imh0dHA6Ly9wdXJsLm9yZy9zeW5kaWNhdGlvbi9hdG9tbWVkaWEiIHhtbG5zOnN0YXR1c25ldD0iaHR0cDovL3N0YXR1cy5uZXQvc2NoZW1hL2FwaS8xLyI-CiA8YWN0aXZpdHk6b2JqZWN0LXR5cGU-aHR0cDovL2FjdGl2aXR5c3RyZWEubXMvc2NoZW1hLzEuMC9ub3RlPC9hY3Rpdml0eTpvYmplY3QtdHlwZT4KIDxpZD5odHRwOi8vaWRlbnRpLmNhL25vdGljZS82NTEzOTc5MjwvaWQ-CiA8dGl0bGU-dGhpcyBvbmUgaXMgZm9yIEBjYXBhQGVzY2hlbmF1ZXIuYmUgLSBlbmpveSAhPC90aXRsZT4KIDxjb250ZW50IHR5cGU9Imh0bWwiPnRoaXMgb25lIGlzIGZvciBAJmx0O3NwYW4gY2xhc3M9JnF1b3Q7dmNhcmQmcXVvdDsmZ3Q7Jmx0O2EgaHJlZj0mcXVvdDtodHRwOi8vZXNjaGVuYXVlci5iZS91c2Vycy9jYXBhJnF1b3Q7IGNsYXNzPSZxdW90O3VybCZxdW90OyZndDsmbHQ7c3BhbiBjbGFzcz0mcXVvdDtmbiBuaWNrbmFtZSZxdW90OyZndDtjYXBhQGVzY2hlbmF1ZXIuYmUmbHQ7L3NwYW4mZ3Q7Jmx0Oy9hJmd0OyZsdDsvc3BhbiZndDsgLSBlbmpveSAhPC9jb250ZW50PgogPGxpbmsgcmVsPSJhbHRlcm5hdGUiIHR5cGU9InRleHQvaHRtbCIgaHJlZj0iaHR0cDovL2lkZW50aS5jYS9ub3RpY2UvNjUxMzk3OTIiLz4KIDxhY3Rpdml0eTp2ZXJiPmh0dHA6Ly9hY3Rpdml0eXN0cmVhLm1zL3NjaGVtYS8xLjAvcG9zdDwvYWN0aXZpdHk6dmVyYj4KIDxwdWJsaXNoZWQ-MjAxMS0wMi0yMlQyMToyMjo0OSswMDowMDwvcHVibGlzaGVkPgogPHVwZGF0ZWQ-MjAxMS0wMi0yMlQyMToyMjo0OSswMDowMDwvdXBkYXRlZD4KIDxhdXRob3I-CiAgPGFjdGl2aXR5Om9iamVjdC10eXBlPmh0dHA6Ly9hY3Rpdml0eXN0cmVhLm1zL3NjaGVtYS8xLjAvcGVyc29uPC9hY3Rpdml0eTpvYmplY3QtdHlwZT4KICA8dXJpPmh0dHA6Ly9pZGVudGkuY2EvdXNlci8zODUyMTY8L3VyaT4KICA8bmFtZT5zaG91dHI8L25hbWU-CiAgPGxpbmsgcmVsPSJhbHRlcm5hdGUiIHR5cGU9InRleHQvaHRtbCIgaHJlZj0iaHR0cDovL2lkZW50aS5jYS9zaG91dHIiLz4KICA8bGluayByZWw9ImF2YXRhciIgdHlwZT0iaW1hZ2UvcG5nIiBtZWRpYTp3aWR0aD0iOTYiIG1lZGlhOmhlaWdodD0iOTYiIGhyZWY9Imh0dHA6Ly90aGVtZS5pZGVudGkuY2EvMC45LjdiZXRhMi9pZGVudGljYS9kZWZhdWx0LWF2YXRhci1wcm9maWxlLnBuZyIvPgogIDxsaW5rIHJlbD0iYXZhdGFyIiB0eXBlPSJpbWFnZS9wbmciIG1lZGlhOndpZHRoPSI0OCIgbWVkaWE6aGVpZ2h0PSI0OCIgaHJlZj0iaHR0cDovL3RoZW1lLmlkZW50aS5jYS8wLjkuN2JldGEyL2lkZW50aWNhL2RlZmF1bHQtYXZhdGFyLXN0cmVhbS5wbmciLz4KICA8bGluayByZWw9ImF2YXRhciIgdHlwZT0iaW1hZ2UvcG5nIiBtZWRpYTp3aWR0aD0iMjQiIG1lZGlhOmhlaWdodD0iMjQiIGhyZWY9Imh0dHA6Ly90aGVtZS5pZGVudGkuY2EvMC45LjdiZXRhMi9pZGVudGljYS9kZWZhdWx0LWF2YXRhci1taW5pLnBuZyIvPgogIDxwb2NvOnByZWZlcnJlZFVzZXJuYW1lPnNob3V0cjwvcG9jbzpwcmVmZXJyZWRVc2VybmFtZT4KICA8cG9jbzpkaXNwbGF5TmFtZT5TaG91dHI8L3BvY286ZGlzcGxheU5hbWU-CiAgPHBvY286dXJscz4KICAgPHBvY286dHlwZT5ob21lcGFnZTwvcG9jbzp0eXBlPgogICA8cG9jbzp2YWx1ZT5odHRwOi8vc2hvdXRyLm9yZzwvcG9jbzp2YWx1ZT4KICAgPHBvY286cHJpbWFyeT50cnVlPC9wb2NvOnByaW1hcnk-CjwvcG9jbzp1cmxzPgo8L2F1dGhvcj4KIDwhLS1EZXByZWNhdGlvbiB3YXJuaW5nOiBhY3Rpdml0eTphY3RvciBpcyBwcmVzZW50IG9ubHkgZm9yIGJhY2t3YXJkIGNvbXBhdGliaWxpdHkuIEl0IHdpbGwgYmUgcmVtb3ZlZCBpbiB0aGUgbmV4dCB2ZXJzaW9uIG9mIFN0YXR1c05ldC4tLT4KIDxhY3Rpdml0eTphY3Rvcj4KICA8YWN0aXZpdHk6b2JqZWN0LXR5cGU-aHR0cDovL2FjdGl2aXR5c3RyZWEubXMvc2NoZW1hLzEuMC9wZXJzb248L2FjdGl2aXR5Om9iamVjdC10eXBlPgogIDxpZD5odHRwOi8vaWRlbnRpLmNhL3VzZXIvMzg1MjE2PC9pZD4KICA8dGl0bGU-U2hvdXRyPC90aXRsZT4KICA8bGluayByZWw9ImFsdGVybmF0ZSIgdHlwZT0idGV4dC9odG1sIiBocmVmPSJodHRwOi8vaWRlbnRpLmNhL3Nob3V0ciIvPgogIDxsaW5rIHJlbD0iYXZhdGFyIiB0eXBlPSJpbWFnZS9wbmciIG1lZGlhOndpZHRoPSI5NiIgbWVkaWE6aGVpZ2h0PSI5NiIgaHJlZj0iaHR0cDovL3RoZW1lLmlkZW50aS5jYS8wLjkuN2JldGEyL2lkZW50aWNhL2RlZmF1bHQtYXZhdGFyLXByb2ZpbGUucG5nIi8-CiAgPGxpbmsgcmVsPSJhdmF0YXIiIHR5cGU9ImltYWdlL3BuZyIgbWVkaWE6d2lkdGg9IjQ4IiBtZWRpYTpoZWlnaHQ9IjQ4IiBocmVmPSJodHRwOi8vdGhlbWUuaWRlbnRpLmNhLzAuOS43YmV0YTIvaWRlbnRpY2EvZGVmYXVsdC1hdmF0YXItc3RyZWFtLnBuZyIvPgogIDxsaW5rIHJlbD0iYXZhdGFyIiB0eXBlPSJpbWFnZS9wbmciIG1lZGlhOndpZHRoPSIyNCIgbWVkaWE6aGVpZ2h0PSIyNCIgaHJlZj0iaHR0cDovL3RoZW1lLmlkZW50aS5jYS8wLjkuN2JldGEyL2lkZW50aWNhL2RlZmF1bHQtYXZhdGFyLW1pbmkucG5nIi8-CiAgPHBvY286cHJlZmVycmVkVXNlcm5hbWU-c2hvdXRyPC9wb2NvOnByZWZlcnJlZFVzZXJuYW1lPgogIDxwb2NvOmRpc3BsYXlOYW1lPlNob3V0cjwvcG9jbzpkaXNwbGF5TmFtZT4KICA8cG9jbzp1cmxzPgogICA8cG9jbzp0eXBlPmhvbWVwYWdlPC9wb2NvOnR5cGU-CiAgIDxwb2NvOnZhbHVlPmh0dHA6Ly9zaG91dHIub3JnPC9wb2NvOnZhbHVlPgogICA8cG9jbzpwcmltYXJ5PnRydWU8L3BvY286cHJpbWFyeT4KPC9wb2NvOnVybHM-CjwvYWN0aXZpdHk6YWN0b3I-CiA8bGluayByZWw9Im9zdGF0dXM6Y29udmVyc2F0aW9uIiBocmVmPSJodHRwOi8vaWRlbnRpLmNhL2NvbnZlcnNhdGlvbi82NDM4Mjk1NSIvPgogPGxpbmsgcmVsPSJvc3RhdHVzOmF0dGVudGlvbiIgaHJlZj0iaHR0cDovL2VzY2hlbmF1ZXIuYmUvdXNlcnMvY2FwYSIvPgogPGxpbmsgcmVsPSJtZW50aW9uZWQiIGhyZWY9Imh0dHA6Ly9lc2NoZW5hdWVyLmJlL3VzZXJzL2NhcGEiLz4KIDxnZW9yc3M6cG9pbnQ-NTAuNTY2NjcgNS41ODMzMzwvZ2VvcnNzOnBvaW50PgogPHNvdXJjZT4KICA8aWQ-aHR0cDovL2VzY2hlbmF1ZXIuYmUvdXBkYXRlcy9jYXBhLmF0b208L2lkPgogIDx0aXRsZT5jYXBhPC90aXRsZT4KICA8bGluayByZWw9ImFsdGVybmF0ZSIgdHlwZT0idGV4dC9odG1sIiBocmVmPSJodHRwOi8vZXNjaGVuYXVlci5iZS91c2Vycy9jYXBhIi8-CiAgPGxpbmsgcmVsPSJzZWxmIiB0eXBlPSJhcHBsaWNhdGlvbi9hdG9tK3htbCIgaHJlZj0iaHR0cDovL2VzY2hlbmF1ZXIuYmUvdXBkYXRlcy9jYXBhLmF0b20iLz4KICA8aWNvbj5odHRwOi8vdGhlbWUuaWRlbnRpLmNhLzAuOS43YmV0YTIvaWRlbnRpY2EvZGVmYXVsdC1hdmF0YXItcHJvZmlsZS5wbmc8L2ljb24-Cjwvc291cmNlPgogPGxpbmsgcmVsPSJzZWxmIiB0eXBlPSJhcHBsaWNhdGlvbi9hdG9tK3htbCIgaHJlZj0iaHR0cDovL2lkZW50aS5jYS9hcGkvc3RhdHVzZXMvc2hvdy82NTEzOTc5Mi5hdG9tIi8-CiA8bGluayByZWw9ImVkaXQiIHR5cGU9ImFwcGxpY2F0aW9uL2F0b20reG1sIiBocmVmPSJodHRwOi8vaWRlbnRpLmNhL2FwaS9zdGF0dXNlcy9zaG93LzY1MTM5NzkyLmF0b20iLz4KIDxzdGF0dXNuZXQ6bm90aWNlX2luZm8gbG9jYWxfaWQ9IjY1MTM5NzkyIiBzb3VyY2U9IndlYiI-PC9zdGF0dXNuZXQ6bm90aWNlX2luZm8-CjwvZW50cnk-Cg==', 19 | data_type: 'application/atom xml', 20 | encoding: 'base64url', 21 | alg: 'RSA-SHA256' 22 | }; 23 | 24 | var key = "RSA.xwUIPhP_nabY5OJ8Ka5T8n8lLnvvUb0DOzd08IniSVwt0AdIJsFmzGKOb6tVfF_IgaNWpWjyx_ek3aHV-U6AgkYcTxoLLaNkUrnHDQWfi6l2s3L9zhSCF9xlRIfWOkuaUARdNwteCWXS5rL82I8Bi1Z6zQ3ULI-OCsk7bRhl65M=.AQAB"; 25 | 26 | 27 | test(); 28 | --------------------------------------------------------------------------------