Compare commits
	
		
			10 Commits
		
	
	
		
			master
			...
			v1.2-profi
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 8471f6b6ca | |||
| f18dab15b7 | |||
| af0ef74f23 | |||
| efae8caf3b | |||
| c152878201 | |||
| abaa59dab0 | |||
| 81b213ec4b | |||
| 8397b8a38c | |||
| 3fedb3d8ad | |||
| 9627e2054e | 
							
								
								
									
										368
									
								
								accounts.js
									
									
									
									
									
								
							
							
						
						
									
										368
									
								
								accounts.js
									
									
									
									
									
								
							| @ -61,19 +61,26 @@ function validateOtp(codeStore, codeId, token) { | |||||||
|   }); |   }); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function getOrCreate(store, username) { | function getOrCreate(store, iss, username) { | ||||||
|   return store.IssuerOauth3OrgAccounts.get(username).then(function (account) { |   // account => profile
 | ||||||
|     if (account) { |   return store.IssuerOauth3OrgProfiles.find({ username: username }).then(filterRejectable).then(function (profile) { | ||||||
|       return account; |     profile = profile && profile[0]; | ||||||
|  |     if (profile) { | ||||||
|  |       return profile; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     account = { |     // TODO profile should be ecdsa256 pub/privkeypair
 | ||||||
|       username:  username, |     var sub = makeB64UrlSafe(crypto.randomBytes(32).toString('base64')); | ||||||
|       accountId: makeB64UrlSafe(crypto.randomBytes(32).toString('base64')), |     profile = { | ||||||
|  |       id: sub + (iss && ('@' + iss) || '') | ||||||
|  |     , username:  username | ||||||
|  |     , sub: sub | ||||||
|  |     , iss: iss | ||||||
|  |     , typ: username ? 'username' : 'profile' | ||||||
|     }; |     }; | ||||||
|     return store.IssuerOauth3OrgAccounts.create(username, account).then(function () { |     return store.IssuerOauth3OrgProfiles.create(profile.id, profile).then(function () { | ||||||
|       // TODO: put sort sort of email notification to the server managers?
 |       // TODO: put sort sort of email notification to the server managers?
 | ||||||
|       return account; |       return profile; | ||||||
|     }); |     }); | ||||||
|   }); |   }); | ||||||
| } | } | ||||||
| @ -167,18 +174,28 @@ function createOtp(store, params) { | |||||||
|     }); |     }); | ||||||
|   }); |   }); | ||||||
| } | } | ||||||
|  | function rejectDeleted(el) { | ||||||
|  |   if (el && (!el.revokedAt && !el.deletedAt)) { | ||||||
|  |     return el; | ||||||
|  |   } | ||||||
|  |   return null; | ||||||
|  | } | ||||||
|  | function filterRejectable(arr) { | ||||||
|  |   return arr.filter(rejectDeleted); | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
| function create(app) { | function create(deps, app) { | ||||||
|   var restful = {}; |   var restful = {}; | ||||||
| 
 | 
 | ||||||
|   restful.sendOtp = function (req, res) { |   restful.sendOtp = function (req, res) { | ||||||
|     var params = req.body; |     var params = req.body; | ||||||
|     var promise = req.getSiteStore().then(function (store) { |     var promise = req.getSiteStore().then(function (store) { | ||||||
|  |       //store.IssuerOauth3OrgProfiles = store.IssuerOauth3OrgProfiles || store.IssuerOauth3OrgAccounts;
 | ||||||
|       return createOtp(store, params).then(function (code) { |       return createOtp(store, params).then(function (code) { | ||||||
|         var emailParams = { |         var emailParams = { | ||||||
|           to:      params.username, |           to:      params.username, | ||||||
|           from:    'login@daplie.com', |           from:    'login@mg.hellabit.com', | ||||||
|           replyTo: 'hello@daplie.com', |           replyTo: 'hello@mg.hellabit.com', | ||||||
|           subject: "Use " + code.code + " as your Login Code", |           subject: "Use " + code.code + " as your Login Code", | ||||||
|           text: "Your login code is:\n\n" |           text: "Your login code is:\n\n" | ||||||
|                 + code.code |                 + code.code | ||||||
| @ -206,6 +223,7 @@ function create(app) { | |||||||
|     var store; |     var store; | ||||||
|     var promise = req.getSiteStore().then(function (_store) { |     var promise = req.getSiteStore().then(function (_store) { | ||||||
|       store = _store; |       store = _store; | ||||||
|  |       //store.IssuerOauth3OrgProfiles = store.IssuerOauth3OrgProfiles || store.IssuerOauth3OrgAccounts;
 | ||||||
|       if (!req.body || !req.body.grant_type) { |       if (!req.body || !req.body.grant_type) { | ||||||
|         throw new OpErr("missing 'grant_type' from the body"); |         throw new OpErr("missing 'grant_type' from the body"); | ||||||
|       } |       } | ||||||
| @ -219,9 +237,20 @@ function create(app) { | |||||||
|       if (req.body.grant_type === 'refresh_token') { |       if (req.body.grant_type === 'refresh_token') { | ||||||
|         return restful.createToken.refreshToken(req); |         return restful.createToken.refreshToken(req); | ||||||
|       } |       } | ||||||
|  |       if (req.body.grant_type === 'exchange_token') { | ||||||
|  |         return restful.createToken.exchangeToken(req); | ||||||
|  |       } | ||||||
| 
 | 
 | ||||||
|       throw new OpErr("unknown or un-implemented grant_type '"+req.body.grant_type+"'"); |       throw new OpErr("unknown or un-implemented grant_type '"+req.body.grant_type+"'"); | ||||||
|     }).then(function (token_info) { |     }).then(function (token_info) { | ||||||
|  |       return restful.createToken._helper(req, res, token_info); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     app.handlePromise(req, res, promise, '[issuer@oauth3.org] create tokens'); | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|  |   restful.createToken._helper = function (req, res, token_info) { | ||||||
|  |     return deps.Promise.resolve().then(function () { | ||||||
|       token_info.iss = req.experienceId; |       token_info.iss = req.experienceId; | ||||||
|       if (!token_info.aud) { |       if (!token_info.aud) { | ||||||
|         throw new OpErr("missing required token field 'aud'"); |         throw new OpErr("missing required token field 'aud'"); | ||||||
| @ -234,17 +263,18 @@ function create(app) { | |||||||
|         // We don't have normal grants for the issuer, so we don't need to look the
 |         // We don't have normal grants for the issuer, so we don't need to look the
 | ||||||
|         // azpSub or the grants up in the database.
 |         // azpSub or the grants up in the database.
 | ||||||
|         token_info.azpSub = token_info.sub; |         token_info.azpSub = token_info.sub; | ||||||
|         token_info.scope = ''; |         token_info.scope = '*'; | ||||||
|         return token_info; |         return token_info; | ||||||
|       } |       } | ||||||
| 
 | 
 | ||||||
|       var search = {}; |       var search = {}; | ||||||
|       ['sub', 'azp', 'azpSub'].forEach(function (key) { |       [ 'sub', 'azp', 'azpSub' ].forEach(function (key) { | ||||||
|         if (token_info[key]) { |         if (token_info[key]) { | ||||||
|           search[key] = token_info[key]; |           search[key] = token_info[key]; | ||||||
|         } |         } | ||||||
|       }); |       }); | ||||||
|       return store.IssuerOauth3OrgGrants.find(search).then(function (grants) { | 
 | ||||||
|  |       return req.Models.IssuerOauth3OrgGrants.find(search).then(filterRejectable).then(function (grants) { | ||||||
|         if (!grants.length) { |         if (!grants.length) { | ||||||
|           throw new OpErr("'"+token_info.azp+"' not given any grants from '"+(token_info.sub || token_info.azpSub)+"'"); |           throw new OpErr("'"+token_info.azp+"' not given any grants from '"+(token_info.sub || token_info.azpSub)+"'"); | ||||||
|         } |         } | ||||||
| @ -258,7 +288,7 @@ function create(app) { | |||||||
|         return token_info; |         return token_info; | ||||||
|       }); |       }); | ||||||
|     }).then(function (token_info) { |     }).then(function (token_info) { | ||||||
|       return getPrivKey(store, req.experienceId).then(function (jwk) { |       return getPrivKey(req.Models, req.experienceId).then(function (jwk) { | ||||||
|         var pem = require('jwk-to-pem')(jwk, { private: true }); |         var pem = require('jwk-to-pem')(jwk, { private: true }); | ||||||
|         var payload =  { |         var payload =  { | ||||||
|           // standard
 |           // standard
 | ||||||
| @ -300,9 +330,49 @@ function create(app) { | |||||||
|         return result; |         return result; | ||||||
|       }); |       }); | ||||||
|     }); |     }); | ||||||
| 
 |  | ||||||
|     app.handlePromise(req, res, promise, '[issuer@oauth3.org] create tokens'); |  | ||||||
|   }; |   }; | ||||||
|  | 
 | ||||||
|  |   // This should exchange:
 | ||||||
|  |   //    * 3rd party PPIDs for 1st Party Profile
 | ||||||
|  |   //    * Opaque Tokens for PPID Tokens
 | ||||||
|  |   restful.createToken.exchangeToken = function (req, res) { | ||||||
|  |     var OAUTH3 = require('./oauth3.js'); | ||||||
|  |     var store = req.Models; | ||||||
|  | 
 | ||||||
|  |     console.log('[exchangeToken] req.oauth3:'); | ||||||
|  |     console.log(req.oauth3); // req.oauth3.encodedToken
 | ||||||
|  | 
 | ||||||
|  |     console.log('[exchangeToken] OAUTH3.jwk:'); | ||||||
|  |     console.log(OAUTH3.jwk); | ||||||
|  | 
 | ||||||
|  |     var promise = OAUTH3.jwk.verifyToken(req.oauth3.encodedToken).then(function (completeDecoded) { | ||||||
|  |       var p; | ||||||
|  | 
 | ||||||
|  |       console.log('[exchangeToken] verified token:'); | ||||||
|  |       console.log(completeDecoded); | ||||||
|  |       // TODO handle opaque tokens by exchanging at issuer -- if (!token.sub && token.jti) { ... }
 | ||||||
|  | 
 | ||||||
|  |       if (!req.body || !req.body.create) { | ||||||
|  |         return Profiles.get(req, res, completeDecoded.payload).then(function (profiles) { | ||||||
|  |           return deps.Promise.all(profiles.map(function (profile) { | ||||||
|  |             return Profiles._getToken(req, res, profile); | ||||||
|  |           })); | ||||||
|  |         }).then(function (tokens) { | ||||||
|  |           return { tokens: tokens }; | ||||||
|  |         }); | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       return Profiles.getOrCreate(req, res, completeDecoded.payload).then(function () { | ||||||
|  |         return Profiles._getToken(req, res, profile).then(function (token) { | ||||||
|  |           return { tokens: [ token ] }; | ||||||
|  |         }); | ||||||
|  |       }); | ||||||
|  | 
 | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     app.handlePromise(req, res, promise, '[issuer@oauth3.org] exchangeToken'); | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|   restful.createToken.password = function (req) { |   restful.createToken.password = function (req) { | ||||||
|     var params = req.body; |     var params = req.body; | ||||||
|     if (!params || !params.username) { |     if (!params || !params.username) { | ||||||
| @ -319,22 +389,24 @@ function create(app) { | |||||||
|     var codeId = crypto.createHash('sha256').update(params.username_type+':'+params.username).digest('base64'); |     var codeId = crypto.createHash('sha256').update(params.username_type+':'+params.username).digest('base64'); | ||||||
|     codeId = makeB64UrlSafe(codeId); |     codeId = makeB64UrlSafe(codeId); | ||||||
|     return req.getSiteStore().then(function (store) { |     return req.getSiteStore().then(function (store) { | ||||||
|  |       //store.IssuerOauth3OrgProfiles = store.IssuerOauth3OrgProfiles || store.IssuerOauth3OrgAccounts;
 | ||||||
|       return validateOtp(store.IssuerOauth3OrgCodes, codeId, params.password) |       return validateOtp(store.IssuerOauth3OrgCodes, codeId, params.password) | ||||||
|       .then(function () { |       .then(function () { | ||||||
|         return getOrCreate(store, params.username); |         return getOrCreate(store, req.experienceId, params.username); | ||||||
|       }).then(function (account) { |       }).then(function (account) { | ||||||
|         var contactClaimId = crypto.createHash('sha256').update(account.accountId+':'+params.username_type+':'+params.username).digest('base64'); |         var contactClaimId = crypto.createHash('sha256').update(account.sub+':'+params.username_type+':'+params.username).digest('base64'); | ||||||
|  |         //var contactClaimId = crypto.createHash('sha256').update(account.accountId+':'+params.username_type+':'+params.username).digest('base64');
 | ||||||
|         return req.Models.IssuerOauth3OrgContactNodes.get(contactClaimId).then(function (contactClaim) { |         return req.Models.IssuerOauth3OrgContactNodes.get(contactClaimId).then(function (contactClaim) { | ||||||
|           var now = Date.now(); |           var now = Date.now(); | ||||||
|           if (!contactClaim) { contactClaim = { id: contactClaimId }; } |           if (!contactClaim) { contactClaim = { id: contactClaimId, accountId: (req.oauth3._IDX_ || req.oauth3.accountIdx) }; } | ||||||
|           if (!contactClaim.verifiedAt) { contactClaim.verifiedAt = now; } |           if (!contactClaim.verifiedAt) { contactClaim.verifiedAt = now; } | ||||||
|           contactClaim.lastVerifiedAt = now; |           contactClaim.lastVerifiedAt = now; | ||||||
| 
 | 
 | ||||||
|           console.log('contactClaim'); |           console.log('[DEBUG] contactClaim'); | ||||||
|           console.log(contactClaim); |           console.log(contactClaim); | ||||||
|           return req.Models.IssuerOauth3OrgContactNodes.upsert(contactClaim).then(function () { |           return req.Models.IssuerOauth3OrgContactNodes.upsert(contactClaim).then(function () { | ||||||
|             return { |             return { | ||||||
|               sub: account.accountId, |               sub: account.sub || account.accountId, | ||||||
|               aud: req.params.aud || req.body.aud || req.experienceId, |               aud: req.params.aud || req.body.aud || req.experienceId, | ||||||
|               azp: req.params.azp || req.body.azp || req.body.client_id || req.body.client_uri || req.experienceId, |               azp: req.params.azp || req.body.azp || req.body.client_id || req.body.client_uri || req.experienceId, | ||||||
|             }; |             }; | ||||||
| @ -371,30 +443,242 @@ function create(app) { | |||||||
|     }); |     }); | ||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
|  |   var Credentials = {}; | ||||||
|  |   Credentials.getOrCreate = function (credential) { | ||||||
|  |     var query = {}; | ||||||
|  |     var id; | ||||||
|  |     var result; | ||||||
|  | 
 | ||||||
|  |     if (credential.username) { | ||||||
|  |       query.username = credential.username; | ||||||
|  |       id = credential.username; | ||||||
|  |     } else if (credential.iss) { | ||||||
|  |       query.sub = credential.sub; | ||||||
|  |       query.iss = credential.iss; | ||||||
|  |       id = query.sub + '@' + query.iss; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return req.Models.IssuerOauth3OrgCredentials.find(query).then(filterRejectable).then(function (_credentials) { | ||||||
|  |       if (_credentials.length) { | ||||||
|  |         return _credentials[0]; | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       result = { | ||||||
|  |         username: credential.username | ||||||
|  |       , sub: credential.sub | ||||||
|  |       , iss: credential.iss | ||||||
|  |       , typ: username ? 'username' : 'profile' | ||||||
|  |       }; | ||||||
|  | 
 | ||||||
|  |       return req.Models.IssuerOauth3OrgCredentials.create(id, result).then(function () { | ||||||
|  |         result.id = id; | ||||||
|  |         return result; | ||||||
|  |       }); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|  |   var Profiles = {}; | ||||||
|  |   Profiles.id = function (token) { | ||||||
|  |     var id = token.sub || ''; | ||||||
|  |     if (token.iss) { | ||||||
|  |       id += '@' + token.iss; | ||||||
|  |     } | ||||||
|  |     return id || token.id || token.accountIdx || token.accountId; | ||||||
|  |   }; | ||||||
|  |   Profiles.create = function (req, res, credential, meta) { | ||||||
|  |     meta = meta || {}; | ||||||
|  |     var pub = meta.sub; | ||||||
|  |     var store = req.Models; | ||||||
|  |     var iss; | ||||||
|  | 
 | ||||||
|  |     if (!meta.sub) { | ||||||
|  |       var bs58 = require('bs58'); | ||||||
|  |       var EC = require('elliptic').ec; | ||||||
|  |       var ec = new EC('secp256k1'); | ||||||
|  |       // TODO should be able to generate a private key without a library
 | ||||||
|  |       // https://crypto.stackexchange.com/a/30273/53868
 | ||||||
|  |       var key = ec.genKeyPair(); | ||||||
|  |       //var ec = new EC('curve25519');
 | ||||||
|  |       pub = bs58.encode(Buffer.from(key.derive(key.getPublic()).toString('hex'), 'hex')); | ||||||
|  |       var priv = bs58.encode(Buffer.from(key.priv.toString('hex'), 'hex')); | ||||||
|  |       iss = req.experienceId; | ||||||
|  |     } else { | ||||||
|  |       iss = credential.iss; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     var id = pub + '@' + iss; | ||||||
|  | 
 | ||||||
|  |     if (!credential.sub) { | ||||||
|  |       return deps.Promise.reject(new Error("missing 'sub' from credential")); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     var profile = { | ||||||
|  |       id: id | ||||||
|  |     //  accountId: pub // profile.sub
 | ||||||
|  |     , sub: pub // profile.sub
 | ||||||
|  |     , iss: iss | ||||||
|  |     , prv: priv | ||||||
|  |     , typ: 'profile' | ||||||
|  |     , username: id | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     console.log('[debug] id, credential, profile:'); | ||||||
|  |     console.log(id); | ||||||
|  |     console.log(credential); | ||||||
|  |     console.log(profile); | ||||||
|  | 
 | ||||||
|  |     console.log('[Profiles.create] profile:'); | ||||||
|  |     console.log(profile); | ||||||
|  |     return store.IssuerOauth3OrgProfiles.create(profile.id/*username*/, profile).then(function () { | ||||||
|  |       console.log('[Profiles.create] created!'); | ||||||
|  |       var id = crypto.randomBytes(16).toString('hex'); | ||||||
|  |       return store.IssuerOauth3OrgCredentialsProfiles.create(id, { | ||||||
|  |         credentialId: Profiles.id(credential) | ||||||
|  |       , profileId: Profiles.id(profile) | ||||||
|  |       }).then(function () { | ||||||
|  |         // TODO: put sort sort of email notification to the server managers?
 | ||||||
|  |         return profile; | ||||||
|  |       }); | ||||||
|  |     }); | ||||||
|  |   }; | ||||||
|  |   Profiles._getToken = function (req, res, token) { | ||||||
|  |     var tokenInfo = { | ||||||
|  |       iat: Math.round(Date.now() / 1000) | ||||||
|  |     , sub: token.sub | ||||||
|  |     , iss: token.iss | ||||||
|  |     , azp: token.iss | ||||||
|  |     , aud: token.iss | ||||||
|  |     }; | ||||||
|  |     return restful.createToken._helper(req, res, tokenInfo); | ||||||
|  |   }; | ||||||
|  |   Profiles.ids = function (req, res, decoded) { | ||||||
|  |     return req.Models.IssuerOauth3OrgCredentialsProfiles.find({ | ||||||
|  |       credentialId: decoded.sub + '@' + decoded.iss | ||||||
|  |     //, sub: decoded.payload.sub
 | ||||||
|  |     //, iss: decoded.payload.iss
 | ||||||
|  |     }).then(filterRejectable).then(function (joins) { | ||||||
|  |       console.log('[exchangeToken] credentials profiles:'); | ||||||
|  |       console.log(joins); | ||||||
|  | 
 | ||||||
|  |       return joins; | ||||||
|  |     }); | ||||||
|  |   }; | ||||||
|  |   Profiles.get = function (req, res, decoded) { | ||||||
|  |     return Profiles.ids(decoded).then(function (joins) { | ||||||
|  |       return Profiles.getFromIds(req, res, joins); | ||||||
|  |     }); | ||||||
|  |   }; | ||||||
|  |   Profiles.getFromIds = function (req, res, joins) { | ||||||
|  |     var query = { id: 'IN ' + joins.map(function (el) { return el.profileId }).join(',') }; | ||||||
|  |     //var query = { username: 'IN ' + joins.map(function (el) { return el.profileId }).join(',') };
 | ||||||
|  |     //var query = { accountId: 'IN ' + joins.map(function (el) { return el.profileId }).join(',') };
 | ||||||
|  |     //var query = { accountId: joins.map(function (el) { return el.profileId })[0] };
 | ||||||
|  |     console.log('[DEBUG] query profiles:'); | ||||||
|  |     console.log(query); | ||||||
|  | 
 | ||||||
|  |     return Profiles._get(req, res, query).then(function (profiles) { | ||||||
|  |       console.log('[DEBUG] Profiles:'); | ||||||
|  |       console.log(profiles); | ||||||
|  | 
 | ||||||
|  |       return profiles; | ||||||
|  |     }); | ||||||
|  |   }; | ||||||
|  |   Profiles._get = function (req, res, query) { | ||||||
|  |     return req.Models.IssuerOauth3OrgProfiles.find(query).then(filterRejectable); | ||||||
|  |   }; | ||||||
|  |   Profiles._one = function (req, res, id) { | ||||||
|  |     console.log('[Profiles._one] id:', id); | ||||||
|  |     return req.Models.IssuerOauth3OrgProfiles.get(id).then(function (p) { | ||||||
|  |       console.log('[Profiles._one] p:', p); | ||||||
|  |       return p; | ||||||
|  |     }).then(rejectDeleted); | ||||||
|  |   }; | ||||||
|  |   Profiles.oneOrCreate = function (req, res, cred) { | ||||||
|  |     var sub; | ||||||
|  |     var iss; | ||||||
|  |     var tok; | ||||||
|  |     if (cred.accountIdx) { | ||||||
|  |       sub = cred.accountIdx.split('@')[0]; | ||||||
|  |       iss = cred.accountIdx.split('@')[1]; | ||||||
|  |     } | ||||||
|  |     tok = { sub: sub || cred.sub, iss: iss || cred.iss }; | ||||||
|  | 
 | ||||||
|  |     var id = Profiles.id(cred); | ||||||
|  |     console.log('[oneOrCreate] id:', id); | ||||||
|  |     return Profiles._one(req, res, id).then(function (profile) { | ||||||
|  |       console.log('[oneOrCreate] profile:', profile); | ||||||
|  |       if (profile) { return profile; } | ||||||
|  | 
 | ||||||
|  |       return Profiles.create(req, res, tok, tok); | ||||||
|  |     }); | ||||||
|  |   }; | ||||||
|  |   Profiles.getOrCreate = function (req, res, cred) { | ||||||
|  |     var sub; | ||||||
|  |     var iss; | ||||||
|  |     var tok; | ||||||
|  |     if (cred.accountIdx) { | ||||||
|  |       sub = cred.accountIdx.split('@')[0]; | ||||||
|  |       iss = cred.accountIdx.split('@')[1]; | ||||||
|  |     } | ||||||
|  |     tok = { sub: sub || cred.sub, iss: iss || cred.iss }; | ||||||
|  | 
 | ||||||
|  |     return Profiles.ids(req, res, cred).then(function (joins) { | ||||||
|  |       if (joins.length) { | ||||||
|  |         console.log('[DEBUG] CredentialsProfiles:'); | ||||||
|  |         console.log(joins); | ||||||
|  |         console.log('[DEBUG] will not create profile'); | ||||||
|  |         return Profiles.getFromIds(req, res, joins); | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       console.log('[DEBUG] will create profile'); | ||||||
|  |       req.body.sub = req.body.sub || cred.sub; | ||||||
|  |       req.body.iss = req.body.iss || cred.iss; | ||||||
|  |       return Profiles.create(req, res, req.oauth3.token, req.body).then(function (profile) { | ||||||
|  |         return [ profile ]; | ||||||
|  |       }); | ||||||
|  |     }); | ||||||
|  |   }; | ||||||
|   restful.getProfile = function (req, res) { |   restful.getProfile = function (req, res) { | ||||||
|     var promise = req.Models.IssuerOauth3OrgAccounts.get(req.oauth3.accountIdx).then(function (result) { |     // return Profiles.getOrCreate();
 | ||||||
|       if (!result) { result = { id: undefined }; } |     console.log('[getProfile] req.oauth3.accountIdx:', req.oauth3.accountIdx); | ||||||
|  |     var promise = req.Models.IssuerOauth3OrgProfiles.get(req.oauth3.accountIdx).then(function (result) { | ||||||
|  |       var err; | ||||||
|  |       if (!result) { | ||||||
|  |         err = new Error( | ||||||
|  |           "No profile exists for '" + req.oauth3.accountIdx + "'. Please create a profile or perform dual-login to link this credential to an existing one." | ||||||
|  |         ); | ||||||
|  |         err.code = 'E_NO_PROFILE@oauth3.org'; | ||||||
|  |         return PromiseA.reject({ message: err.message, code: err.code }); | ||||||
|  |         //return { id: undefined, sub: req.oauth3.accountIdx.split('@')[0], iss: req.oauth3.accountIdx.split('@')[1] };
 | ||||||
|  |       } | ||||||
| 
 | 
 | ||||||
|       result.id = undefined; |       result.id = undefined; | ||||||
|  |       //result.prv = undefined;
 | ||||||
| 
 | 
 | ||||||
|       return result; |       return req.Models.IssuerOauth3OrgContactNodes.find({ accountId: req.oauth3.accountIdx }).then(filterRejectable).then(function (nodes) { | ||||||
|  |         result.nodes = nodes; | ||||||
|  |         return result; | ||||||
|  |       }); | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     app.handlePromise(req, res, promise, '[issuer@oauth3.org] get profile'); |     app.handlePromise(req, res, promise, '[issuer@oauth3.org] get profile'); | ||||||
|   }; |   }; | ||||||
|   restful.setProfile = function (req, res) { |   restful.setProfile = function (req, res) { | ||||||
|     console.log('req.oauth3'); |     console.log('[setProfile] req.oauth3:'); | ||||||
|     console.log(req.oauth3); |     console.log(req.oauth3); | ||||||
| 
 | 
 | ||||||
|     var body = req.body; |     var body = req.body; | ||||||
|     var promise = req.Models.IssuerOauth3OrgAccounts.find({ accountId: req.oauth3.ppid }).then(function (results) { |     var query = { accountIdx: req.oauth3.accountIdx, sub: req.oauth3.accountIdx.split('@')[0], iss: req.oauth3.accountIdx.split('@')[1] }; | ||||||
|       var result = results[0]; |     // was previously accountIdx, which should have been sub@iss anyway...
 | ||||||
|  |     var promise = Profiles.oneOrCreate(req, res, query).then(function (result) { | ||||||
|       var changed = false; |       var changed = false; | ||||||
| 
 | 
 | ||||||
|       console.log('get gotten'); |       console.log('[setProfile] get gotten:'); | ||||||
|       console.log(results); |       console.log(result); | ||||||
| 
 | 
 | ||||||
|       if (!result) { throw new OpErr("account could not be found"); /*result = { accountId: req.oauth3.accountIdx, displayName: '', firstName: '', lastName: '', avatarUrl: '' };*/ } |       if (!result) { throw new OpErr("profile could not be found"); /*result = { accountId: req.oauth3.accountIdx, displayName: '', firstName: '', lastName: '', avatarUrl: '' };*/ } | ||||||
| 
 | 
 | ||||||
|       // TODO schema for validation
 |       // TODO schema for validation
 | ||||||
|       [ 'firstName', 'lastName', 'avatarUrl', 'displayName' ].forEach(function (key) { |       [ 'firstName', 'lastName', 'avatarUrl', 'displayName' ].forEach(function (key) { | ||||||
| @ -406,18 +690,33 @@ function create(app) { | |||||||
|         } |         } | ||||||
|       }); |       }); | ||||||
| 
 | 
 | ||||||
|  |       if (body.email && (result.email !== body.email)) { | ||||||
|  |         if (result.unverifiedEmail !== body.email) { | ||||||
|  |           changed = true; | ||||||
|  |           result.unverifiedEmail = body.email; | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|       if (changed) { |       if (changed) { | ||||||
|         return req.Models.IssuerOauth3OrgAccounts.upsert(result).then(function () { console.log('update updated'); return result; }); |         return req.Models.IssuerOauth3OrgProfiles.upsert(result).then(function () { console.log('[setProfile] update updated'); return result; }); | ||||||
|       } |       } | ||||||
| 
 | 
 | ||||||
|       return result; |       return result; | ||||||
|     }).then(function (result) { |     }).then(function (result) { | ||||||
|       result.id = undefined; |       result.id = undefined; | ||||||
|  |       //result.prv = undefined;
 | ||||||
|  |       return result; | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     app.handlePromise(req, res, promise, '[issuer@oauth3.org] set profile'); |     app.handlePromise(req, res, promise, '[issuer@oauth3.org] set profile'); | ||||||
|   }; |   }; | ||||||
|   restful.listContactNodes = function (req, res) { |   restful.listContactNodes = function (req, res) { | ||||||
|  |     /* | ||||||
|  |     var contactClaimId = crypto.createHash('sha256').update((req.oauth3._IDX_ || req.oauth3.accountIdx)+':'+code.node.type+':'+code.node.node).digest('base64'); | ||||||
|  |     return req.Models.IssuerOauth3OrgContactNodes.get(contactClaimId).then(function (contactClaim) { | ||||||
|  |       return; | ||||||
|  |     }); | ||||||
|  |     */ | ||||||
|   }; |   }; | ||||||
|   restful.claimContact = function (req, res) { |   restful.claimContact = function (req, res) { | ||||||
|     var type = req.body.type; |     var type = req.body.type; | ||||||
| @ -458,7 +757,8 @@ function create(app) { | |||||||
|         throw new OpErr("code didn't have contact node and type information"); |         throw new OpErr("code didn't have contact node and type information"); | ||||||
|       } |       } | ||||||
| 
 | 
 | ||||||
|       var contactClaimId = crypto.createHash('sha256').update(req.oauth3.accountIdx+':'+code.node.type+':'+code.node.node).digest('base64'); |       // TODO this token may represent a 3rd-party credential or 1st-party profile. What should the ID be?
 | ||||||
|  |       var contactClaimId = crypto.createHash('sha256').update((req.oauth3._IDX_ || req.oauth3.accountIdx)+':'+code.node.type+':'+code.node.node).digest('base64'); | ||||||
|       return req.Models.IssuerOauth3OrgContactNodes.get(contactClaimId).then(function (contactClaim) { |       return req.Models.IssuerOauth3OrgContactNodes.get(contactClaimId).then(function (contactClaim) { | ||||||
|         var now = Date.now(); |         var now = Date.now(); | ||||||
|         if (!contactClaim) { contactClaim = { id: contactClaimId }; } |         if (!contactClaim) { contactClaim = { id: contactClaimId }; } | ||||||
|  | |||||||
							
								
								
									
										26
									
								
								models.js
									
									
									
									
									
								
							
							
						
						
									
										26
									
								
								models.js
									
									
									
									
									
								
							| @ -1,7 +1,7 @@ | |||||||
| 'use strict'; | 'use strict'; | ||||||
| 
 | 
 | ||||||
| var apiname = 'issuer_oauth3_org'; | var apiname = 'issuer_oauth3_org'; | ||||||
| var baseFields = [ 'createdAt', 'updatedAt', 'deletedAt' ]; | var baseFields = [ 'createdAt', 'updatedAt', 'deletedAt', 'revokedAt', 'insertedAt' ]; | ||||||
| 
 | 
 | ||||||
| module.exports = [ | module.exports = [ | ||||||
|   { |   { | ||||||
| @ -15,14 +15,28 @@ module.exports = [ | |||||||
|     indices: baseFields.concat([ 'code', 'expires' ]), |     indices: baseFields.concat([ 'code', 'expires' ]), | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|     tablename: apiname + '_accounts', |     tablename: apiname + '_credentials', | ||||||
|     idname: 'username', |     idname: 'id', | ||||||
|     indices: baseFields.concat([ 'accountId' ]), |     // credentialId = ppid@iss
 | ||||||
|  |     indices: baseFields.concat([ 'username', 'sub', 'iss', 'typ', 'salt', 'shadow' ]), // comment, recoveryCredential
 | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     tablename: apiname + '_credentials_profiles', | ||||||
|  |     idname: 'id', | ||||||
|  |     // credentialId = ppid@iss
 | ||||||
|  |     indices: baseFields.concat([ 'credentialId', 'profileId' ]), | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     tablename: apiname + '_profiles', | ||||||
|  |     idname: 'id', | ||||||
|  |     // make sub an ecdsa256 key
 | ||||||
|  |     indices: baseFields.concat([ 'username', 'sub', 'iss', 'typ', 'privateKey' ]), // comment, recoveryNodes
 | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|     tablename: apiname + '_contact_nodes', |     tablename: apiname + '_contact_nodes', | ||||||
|     idname: 'id', |     idname: 'id', | ||||||
|     indices: baseFields.concat([ 'accountId', 'verifiedAt', 'lastVerifiedAt' ]), |     // contact nodes could apply to either credential or profile?
 | ||||||
|  |     indices: baseFields.concat([ 'accountId', 'priority', 'verifiedAt', 'lastVerifiedAt' ]), | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|     tablename: apiname + '_jwks', |     tablename: apiname + '_jwks', | ||||||
| @ -33,5 +47,5 @@ module.exports = [ | |||||||
|     tablename: apiname + '_grants', |     tablename: apiname + '_grants', | ||||||
|     idname: 'id', |     idname: 'id', | ||||||
|     indices: baseFields.concat([ 'sub', 'azp', 'azpSub', 'scope' ]), |     indices: baseFields.concat([ 'sub', 'azp', 'azpSub', 'scope' ]), | ||||||
|   }, |   } | ||||||
| ]; | ]; | ||||||
|  | |||||||
| @ -12,6 +12,7 @@ | |||||||
|   }, |   }, | ||||||
|   "dependencies": { |   "dependencies": { | ||||||
|     "bluebird": "^3.5.0", |     "bluebird": "^3.5.0", | ||||||
|  |     "bs58": "^4.0.1", | ||||||
|     "elliptic": "^6.4.0", |     "elliptic": "^6.4.0", | ||||||
|     "jsonwebtoken": "^7.4.1", |     "jsonwebtoken": "^7.4.1", | ||||||
|     "jwk-to-pem": "^1.2.6", |     "jwk-to-pem": "^1.2.6", | ||||||
|  | |||||||
							
								
								
									
										10
									
								
								rest.js
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								rest.js
									
									
									
									
									
								
							| @ -3,7 +3,7 @@ | |||||||
| module.exports.create = function (bigconf, deps, app) { | module.exports.create = function (bigconf, deps, app) { | ||||||
|   var Jwks = require('./jwks').create(app); |   var Jwks = require('./jwks').create(app); | ||||||
|   var Grants = require('./grants').create(app); |   var Grants = require('./grants').create(app); | ||||||
|   var Accounts = require('./accounts').create(app); |   var Accounts = require('./accounts').create(deps, app); | ||||||
| 
 | 
 | ||||||
|   // This tablename is based on the tablename found in the objects in model.js.
 |   // This tablename is based on the tablename found in the objects in model.js.
 | ||||||
|   // Instead of the snake_case the name with be UpperCammelCase, converted by masterquest-sqlite3.
 |   // Instead of the snake_case the name with be UpperCammelCase, converted by masterquest-sqlite3.
 | ||||||
| @ -48,11 +48,15 @@ module.exports.create = function (bigconf, deps, app) { | |||||||
|   app.post(  '/access_token/:sub/:aud/:azp',    Accounts.restful.createToken); |   app.post(  '/access_token/:sub/:aud/:azp',    Accounts.restful.createToken); | ||||||
|   app.post(  '/access_token',                   Accounts.restful.createToken); |   app.post(  '/access_token',                   Accounts.restful.createToken); | ||||||
| 
 | 
 | ||||||
|   app.use(   '/acl/profile',                    attachSiteModels); |   app.use(   '/exchange_token',                 attachSiteModels); | ||||||
|  |   app.post(  '/exchange_token',                 Accounts.restful.createToken.exchangeToken); | ||||||
|  | 
 | ||||||
|  |   // TODO secure ACL endpoints with proper grants
 | ||||||
|  |   app.use(   '/acl/profile',                    attachSiteModels, /*app.grantsRequired(['profile@oauth3.org'])*/); | ||||||
|   app.get(   '/acl/profile',                    Accounts.restful.getProfile); |   app.get(   '/acl/profile',                    Accounts.restful.getProfile); | ||||||
|   app.post(  '/acl/profile',                    Accounts.restful.setProfile); |   app.post(  '/acl/profile',                    Accounts.restful.setProfile); | ||||||
| 
 | 
 | ||||||
|   app.use(   '/acl/contact_nodes',              attachSiteModels); |   app.use(   '/acl/contact_nodes',              attachSiteModels, /*app.grantsRequired(['profile@oauth3.org'])*/); | ||||||
|   app.post(  '/acl/contact_nodes',              Accounts.restful.claimContact); |   app.post(  '/acl/contact_nodes',              Accounts.restful.claimContact); | ||||||
|   app.post(  '/acl/contact_nodes/:id',          Accounts.restful.verifyContact); |   app.post(  '/acl/contact_nodes/:id',          Accounts.restful.verifyContact); | ||||||
| 
 | 
 | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user