maingate 이전
This commit is contained in:
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
.vscode/
|
||||||
|
__debug_bin.exe
|
||||||
|
*.log
|
||||||
|
*.exe
|
||||||
64
config.json
Normal file
64
config.json
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
{
|
||||||
|
"region_storage" : {
|
||||||
|
"private" : {
|
||||||
|
"mongo" : "mongodb://192.168.8.94:27017/?replicaSet=repl01&retrywrites=false",
|
||||||
|
"redis" : {
|
||||||
|
"url" : "redis://192.168.8.94:6379",
|
||||||
|
"offset" : {
|
||||||
|
"cache" : 0,
|
||||||
|
"session" : 1,
|
||||||
|
"ranking" : 2,
|
||||||
|
"chat" : 3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"dev" : {
|
||||||
|
"mongo" : "mongodb://192.168.8.94:27017/?replicaSet=repl01&retrywrites=false",
|
||||||
|
"redis" : {
|
||||||
|
"url" : "redis://192.168.8.94:6379",
|
||||||
|
"offset" : {
|
||||||
|
"cache" : 0,
|
||||||
|
"session" : 1,
|
||||||
|
"ranking" : 2,
|
||||||
|
"chat" : 3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"maingate_mongodb_url" : "mongodb://192.168.8.94:27017/?replicaSet=repl01&retrywrites=false",
|
||||||
|
"maingate_service_url" : "http://localhost/maingate",
|
||||||
|
"maingate_global_admins" : [
|
||||||
|
"rehjinh@action2quare.com"
|
||||||
|
],
|
||||||
|
"tavern_service_url" : "http://localhost/tavern",
|
||||||
|
"port" : 8080,
|
||||||
|
|
||||||
|
"postoffice_mongodb_url" : "mongodb://192.168.8.94:27017/?replicaSet=repl01&retrywrites=false",
|
||||||
|
"postoffice_service_url" : "http://localhost/maingate",
|
||||||
|
|
||||||
|
"autologin_ttl": 604800,
|
||||||
|
"redirect_base_url": "https://auth.action2quare.com",
|
||||||
|
"google_client_id" : "46698421246-fv2k7chr1j95ju1vm10ogq8prkjt8272.apps.googleusercontent.com",
|
||||||
|
"google_client_secret" : "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",
|
||||||
|
|
||||||
|
"twitter_oauth_key": "1045586021293862912-MSaciEnwsyNvgfjtmIjGTLA882X8DG",
|
||||||
|
"twitter_oauth_secret": "eclZpTlRQM3igThV7rVoVM5WnyJq9Eu9KfB2bWqvCIzUX",
|
||||||
|
"twitter_customer_key": "mZTsefCkkiwa3Qgj2WYbAGdS5",
|
||||||
|
"twitter_customer_secret": "300zHEJu17JuFupvigdi5s1B5nSiEAO1JqtcX9ZDMnUBJ8Bn6q",
|
||||||
|
|
||||||
|
"apple_client_id": "auth.service.action2quare.com",
|
||||||
|
"apple_privatekey": "-----BEGIN PRIVATE KEY-----\nMIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQgi3HkbNY93XdcNJVa\nAzR895cHxPXYeT0HnAOCzW5IOlOgCgYIKoZIzj0DAQehRANCAAS81nJzJcsZWtdr\n7sAeyqyHFoyCBdmsqI3fvLWYj/5M3MqgMI7pYyVbSmtXT9El/67Y4vz2e/9gllLy\ns0S/XoFo\n-----END PRIVATE KEY-----",
|
||||||
|
"apple_service_id": "auth.service.action2quare.com",
|
||||||
|
"apple_team_id": "YC94S4Z6CS",
|
||||||
|
"apple_key_id": "47UBTLARC8",
|
||||||
|
|
||||||
|
"microsoft_client_id": "ebc03204-a5b4-41bf-ac2b-5051615ccf33",
|
||||||
|
"microsoft_client_secret" : "fa78Q~9C4zEadeOf5ACSFsenP35jHVLKdW.jvcNr",
|
||||||
|
|
||||||
|
"gamepot_project_id": "dbfe1334-6dde-43e0-b8a9-cc0733d4c60e",
|
||||||
|
"gamepot_logincheckapi_url": "https://gamepot.apigw.ntruss.com/gpapps/v1/loginauth",
|
||||||
|
|
||||||
|
"firebase_admin_sdk_credentialfile": "kingdom-2b812-firebase-adminsdk-a6j68-d42ae01182.json"
|
||||||
|
|
||||||
|
}
|
||||||
1
config_template.json
Normal file
1
config_template.json
Normal file
@ -0,0 +1 @@
|
|||||||
|
{}
|
||||||
26
console/asset-manifest.json
Normal file
26
console/asset-manifest.json
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"files": {
|
||||||
|
"main.css": "https://auth.action2quare.com/console/static/css/main.57f4cd54.css",
|
||||||
|
"main.js": "https://auth.action2quare.com/console/static/js/main.2ea4d7d1.js",
|
||||||
|
"static/js/811.8f231000.chunk.js": "https://auth.action2quare.com/console/static/js/811.8f231000.chunk.js",
|
||||||
|
"static/js/514.99ab97af.chunk.js": "https://auth.action2quare.com/console/static/js/514.99ab97af.chunk.js",
|
||||||
|
"static/css/617.e367b0e1.chunk.css": "https://auth.action2quare.com/console/static/css/617.e367b0e1.chunk.css",
|
||||||
|
"static/js/617.ef9b57b1.chunk.js": "https://auth.action2quare.com/console/static/js/617.ef9b57b1.chunk.js",
|
||||||
|
"static/js/348.a88c7705.chunk.js": "https://auth.action2quare.com/console/static/js/348.a88c7705.chunk.js",
|
||||||
|
"static/js/297.8ae1e5e0.chunk.js": "https://auth.action2quare.com/console/static/js/297.8ae1e5e0.chunk.js",
|
||||||
|
"static/js/340.ce8215ad.chunk.js": "https://auth.action2quare.com/console/static/js/340.ce8215ad.chunk.js",
|
||||||
|
"static/js/18.7ddf5096.chunk.js": "https://auth.action2quare.com/console/static/js/18.7ddf5096.chunk.js",
|
||||||
|
"static/js/664.556c2c72.chunk.js": "https://auth.action2quare.com/console/static/js/664.556c2c72.chunk.js",
|
||||||
|
"static/css/691.57000fb1.chunk.css": "https://auth.action2quare.com/console/static/css/691.57000fb1.chunk.css",
|
||||||
|
"static/js/691.b7fcc27d.chunk.js": "https://auth.action2quare.com/console/static/js/691.b7fcc27d.chunk.js",
|
||||||
|
"static/js/491.6101d763.chunk.js": "https://auth.action2quare.com/console/static/js/491.6101d763.chunk.js",
|
||||||
|
"static/js/998.7113153c.chunk.js": "https://auth.action2quare.com/console/static/js/998.7113153c.chunk.js",
|
||||||
|
"static/js/370.79fc8df3.chunk.js": "https://auth.action2quare.com/console/static/js/370.79fc8df3.chunk.js",
|
||||||
|
"static/js/176.44035bcc.chunk.js": "https://auth.action2quare.com/console/static/js/176.44035bcc.chunk.js",
|
||||||
|
"index.html": "https://auth.action2quare.com/console/index.html"
|
||||||
|
},
|
||||||
|
"entrypoints": [
|
||||||
|
"static/css/main.57f4cd54.css",
|
||||||
|
"static/js/main.2ea4d7d1.js"
|
||||||
|
]
|
||||||
|
}
|
||||||
BIN
console/favicon.ico
Normal file
BIN
console/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
1
console/index.html
Normal file
1
console/index.html
Normal file
@ -0,0 +1 @@
|
|||||||
|
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="https://auth.action2quare.com/console/favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="Maingate Console"/><link rel="apple-touch-icon" href="https://auth.action2quare.com/console/logo192.png"/><link rel="manifest" href="https://auth.action2quare.com/console/manifest.json"/><title>Maingate Console</title><script defer="defer" src="https://auth.action2quare.com/console/static/js/main.2ea4d7d1.js"></script><link href="https://auth.action2quare.com/console/static/css/main.57f4cd54.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>
|
||||||
BIN
console/logo192.png
Normal file
BIN
console/logo192.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 23 KiB |
BIN
console/logo512.png
Normal file
BIN
console/logo512.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 23 KiB |
25
console/manifest.json
Normal file
25
console/manifest.json
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"short_name": "Maingate Console",
|
||||||
|
"name": "Maingate Console",
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "favicon.ico",
|
||||||
|
"sizes": "64x64 32x32 24x24 16x16",
|
||||||
|
"type": "image/x-icon"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "logo192.png",
|
||||||
|
"type": "image/png",
|
||||||
|
"sizes": "192x192"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "logo512.png",
|
||||||
|
"type": "image/png",
|
||||||
|
"sizes": "512x512"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"start_url": ".",
|
||||||
|
"display": "standalone",
|
||||||
|
"theme_color": "#000000",
|
||||||
|
"background_color": "#ffffff"
|
||||||
|
}
|
||||||
3
console/robots.txt
Normal file
3
console/robots.txt
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# https://www.robotstxt.org/robotstxt.html
|
||||||
|
User-agent: *
|
||||||
|
Disallow:
|
||||||
1
console/static/css/617.e367b0e1.chunk.css
Normal file
1
console/static/css/617.e367b0e1.chunk.css
Normal file
@ -0,0 +1 @@
|
|||||||
|
[data-simplebar]{align-content:flex-start;align-items:flex-start;flex-direction:column;flex-wrap:wrap;justify-content:flex-start;position:relative}.simplebar-wrapper{height:inherit;max-height:inherit;max-width:inherit;overflow:hidden;width:inherit}.simplebar-mask{direction:inherit;height:auto!important;overflow:hidden;width:auto!important;z-index:0}.simplebar-mask,.simplebar-offset{bottom:0;left:0;margin:0;padding:0;position:absolute;right:0;top:0}.simplebar-offset{-webkit-overflow-scrolling:touch;box-sizing:inherit!important;direction:inherit!important;resize:none!important}.simplebar-content-wrapper{-ms-overflow-style:none;box-sizing:border-box!important;direction:inherit;display:block;height:100%;max-height:100%;max-width:100%;position:relative;scrollbar-width:none;width:auto}.simplebar-content-wrapper::-webkit-scrollbar,.simplebar-hide-scrollbar::-webkit-scrollbar{display:none;height:0;width:0}.simplebar-content:after,.simplebar-content:before{content:" ";display:table}.simplebar-placeholder{max-height:100%;max-width:100%;pointer-events:none;width:100%}.simplebar-height-auto-observer-wrapper{box-sizing:inherit!important;flex-basis:0;flex-grow:inherit;flex-shrink:0;float:left;height:100%;margin:0;max-height:1px;max-width:1px;overflow:hidden;padding:0;pointer-events:none;position:relative;width:100%;z-index:-1}.simplebar-height-auto-observer{box-sizing:inherit;display:block;height:1000%;left:0;min-height:1px;min-width:1px;opacity:0;top:0;width:1000%;z-index:-1}.simplebar-height-auto-observer,.simplebar-track{overflow:hidden;pointer-events:none;position:absolute}.simplebar-track{bottom:0;right:0;z-index:1}[data-simplebar].simplebar-dragging .simplebar-content{pointer-events:none;user-select:none;-webkit-user-select:none}[data-simplebar].simplebar-dragging .simplebar-track{pointer-events:all}.simplebar-scrollbar{left:0;min-height:10px;position:absolute;right:0}.simplebar-scrollbar:before{background:#000;border-radius:7px;content:"";left:2px;opacity:0;position:absolute;right:2px;transition:opacity .2s linear}.simplebar-scrollbar.simplebar-visible:before{opacity:.5;transition:opacity 0s linear}.simplebar-track.simplebar-vertical{top:0;width:11px}.simplebar-track.simplebar-vertical .simplebar-scrollbar:before{bottom:2px;top:2px}.simplebar-track.simplebar-horizontal{height:11px;left:0}.simplebar-track.simplebar-horizontal .simplebar-scrollbar:before{height:100%;left:2px;right:2px}.simplebar-track.simplebar-horizontal .simplebar-scrollbar{height:7px;left:0;min-height:0;min-width:10px;right:auto;top:2px;width:auto}[data-simplebar-direction=rtl] .simplebar-track.simplebar-vertical{left:0;right:auto}.hs-dummy-scrollbar-size{direction:rtl;height:500px;opacity:0;overflow-x:scroll;overflow-y:hidden;position:fixed;visibility:hidden;width:500px}.simplebar-hide-scrollbar{-ms-overflow-style:none;left:0;overflow-y:scroll;position:fixed;scrollbar-width:none;visibility:hidden}
|
||||||
1
console/static/css/691.57000fb1.chunk.css
Normal file
1
console/static/css/691.57000fb1.chunk.css
Normal file
File diff suppressed because one or more lines are too long
7
console/static/css/main.57f4cd54.css
Normal file
7
console/static/css/main.57f4cd54.css
Normal file
File diff suppressed because one or more lines are too long
2
console/static/js/176.44035bcc.chunk.js
Normal file
2
console/static/js/176.44035bcc.chunk.js
Normal file
File diff suppressed because one or more lines are too long
1
console/static/js/176.44035bcc.chunk.js.LICENSE.txt
Normal file
1
console/static/js/176.44035bcc.chunk.js.LICENSE.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
/*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/facebook/regenerator/blob/main/LICENSE */
|
||||||
1
console/static/js/18.7ddf5096.chunk.js
Normal file
1
console/static/js/18.7ddf5096.chunk.js
Normal file
File diff suppressed because one or more lines are too long
1
console/static/js/297.8ae1e5e0.chunk.js
Normal file
1
console/static/js/297.8ae1e5e0.chunk.js
Normal file
File diff suppressed because one or more lines are too long
1
console/static/js/340.ce8215ad.chunk.js
Normal file
1
console/static/js/340.ce8215ad.chunk.js
Normal file
File diff suppressed because one or more lines are too long
1
console/static/js/348.a88c7705.chunk.js
Normal file
1
console/static/js/348.a88c7705.chunk.js
Normal file
File diff suppressed because one or more lines are too long
2
console/static/js/370.79fc8df3.chunk.js
Normal file
2
console/static/js/370.79fc8df3.chunk.js
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
/*! For license information please see 370.79fc8df3.chunk.js.LICENSE.txt */
|
||||||
|
"use strict";(self.webpackChunkmaingate_console=self.webpackChunkmaingate_console||[]).push([[370],{22370:function(e,t,r){r.d(t,{Z:function(){return m}});var n=r(47313),o=function(){return o=Object.assign||function(e){for(var t,r=1,n=arguments.length;r<n;r++)for(var o in t=arguments[r])Object.prototype.hasOwnProperty.call(t,o)&&(e[o]=t[o]);return e},o.apply(this,arguments)};var s={exports:{}};var a,i,c,p;function l(){if(i)return a;i=1;return a="SECRET_DO_NOT_PASS_THIS_OR_YOU_WILL_BE_FIRED"}s.exports=function(){if(p)return c;p=1;var e=l();function t(){}function r(){}return r.resetWarningCache=t,c=function(){function n(t,r,n,o,s,a){if(a!==e){var i=new Error("Calling PropTypes validators directly is not supported by the `prop-types` package. Use PropTypes.checkPropTypes() to call them. Read more at http://fb.me/use-check-prop-types");throw i.name="Invariant Violation",i}}function o(){return n}n.isRequired=n;var s={array:n,bigint:n,bool:n,func:n,number:n,object:n,string:n,symbol:n,any:n,arrayOf:o,element:n,elementType:n,instanceOf:o,node:n,objectOf:o,oneOf:o,oneOfType:o,shape:o,exact:o,checkPropTypes:r,resetWarningCache:t};return s.PropTypes=s,s}}()();var u,f={exports:{}};u=f,function(){var e={}.hasOwnProperty;function t(){for(var r=[],n=0;n<arguments.length;n++){var o=arguments[n];if(o){var s=typeof o;if("string"===s||"number"===s)r.push(o);else if(Array.isArray(o)){if(o.length){var a=t.apply(null,o);a&&r.push(a)}}else if("object"===s)if(o.toString===Object.prototype.toString)for(var i in o)e.call(o,i)&&o[i]&&r.push(i);else r.push(o.toString())}}return r.join(" ")}u.exports?(t.default=t,u.exports=t):window.classNames=t}();var y=f.exports,m=(0,n.forwardRef)((function(e,t){var r,s=e.className,a=e.content,i=e.customClassName,c=e.height,p=e.icon,l=e.name,u=e.size,f=e.title,m=e.use,g=e.width,h=function(e,t){var r={};for(var n in e)Object.prototype.hasOwnProperty.call(e,n)&&t.indexOf(n)<0&&(r[n]=e[n]);if(null!=e&&"function"===typeof Object.getOwnPropertySymbols){var o=0;for(n=Object.getOwnPropertySymbols(e);o<n.length;o++)t.indexOf(n[o])<0&&Object.prototype.propertyIsEnumerable.call(e,n[o])&&(r[n[o]]=e[n[o]])}return r}(e,["className","content","customClassName","height","icon","name","size","title","use","width"]),x=(0,n.useState)(0),v=x[0],w=x[1],O=p||a||l;a&&process,l&&process,(0,n.useMemo)((function(){return w(v+1)}),[O,JSON.stringify(O)]);var b=(0,n.useMemo)((function(){return O&&"string"===typeof O&&O.includes("-")?O.replace(/([-_][a-z0-9])/gi,(function(e){return e.toUpperCase()})).replace(/-/gi,""):O}),[v]),d=f?"<title>".concat(f,"</title>"):"",T=(0,n.useMemo)((function(){return Array.isArray(O)?O:"string"===typeof O&&n.icons?n.icons[b]:void 0}),[v]),_=(0,n.useMemo)((function(){return Array.isArray(T)?T[1]||T[0]:T}),[v]),N=Array.isArray(T)&&T.length>1?T[0]:"64 64",S=h.viewBox||"0 0 ".concat(N),j=i?y(i):y("icon",((r={})["icon-".concat(u)]=u,r["icon-custom-size"]=c||g,r),s);return m?n.createElement("svg",o({xmlns:"http://www.w3.org/2000/svg",className:j},c&&{height:c},g&&{width:g},{role:"img"},h,{ref:t}),n.createElement("use",{href:m})):n.createElement("svg",o({xmlns:"http://www.w3.org/2000/svg",viewBox:S,className:j},c&&{height:c},g&&{width:g},{role:"img",dangerouslySetInnerHTML:{__html:d+_}},h,{ref:t}))}));m.propTypes={className:s.exports.string,content:s.exports.oneOfType([s.exports.array,s.exports.string]),customClassName:s.exports.string,height:s.exports.number,icon:s.exports.oneOfType([s.exports.array,s.exports.string]),name:s.exports.string,size:s.exports.oneOf(["custom","custom-size","sm","lg","xl","xxl","3xl","4xl","5xl","6xl","7xl","8xl","9xl"]),title:s.exports.any,use:s.exports.any,width:s.exports.number},m.displayName="CIcon"}}]);
|
||||||
14
console/static/js/370.79fc8df3.chunk.js.LICENSE.txt
Normal file
14
console/static/js/370.79fc8df3.chunk.js.LICENSE.txt
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
/*!
|
||||||
|
Copyright (c) 2018 Jed Watson.
|
||||||
|
Licensed under the MIT License (MIT), see
|
||||||
|
http://jedwatson.github.io/classnames
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** @license React v16.13.1
|
||||||
|
* react-is.development.js
|
||||||
|
*
|
||||||
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
1
console/static/js/491.6101d763.chunk.js
Normal file
1
console/static/js/491.6101d763.chunk.js
Normal file
File diff suppressed because one or more lines are too long
1
console/static/js/514.99ab97af.chunk.js
Normal file
1
console/static/js/514.99ab97af.chunk.js
Normal file
@ -0,0 +1 @@
|
|||||||
|
"use strict";(self.webpackChunkmaingate_console=self.webpackChunkmaingate_console||[]).push([[514],{82514:function(e,s,l){l.r(s),l.d(s,{default:function(){return i}});l(47313);var c=l(27998),a=l(22370),n=["512 512","<path fill='var(--ci-primary-color, currentColor)' d='M479.6,399.716l-81.084-81.084-62.368-25.767A175.014,175.014,0,0,0,368,192c0-97.047-78.953-176-176-176S16,94.953,16,192,94.953,368,192,368a175.034,175.034,0,0,0,101.619-32.377l25.7,62.2L400.4,478.911a56,56,0,1,0,79.2-79.195ZM48,192c0-79.4,64.6-144,144-144s144,64.6,144,144S271.4,336,192,336,48,271.4,48,192ZM456.971,456.284a24.028,24.028,0,0,1-33.942,0l-76.572-76.572-23.894-57.835L380.4,345.771l76.573,76.572A24.028,24.028,0,0,1,456.971,456.284Z' class='ci-primary'/>"],r=l(46417);function i(){return(0,r.jsx)("div",{className:"bg-light min-vh-100 d-flex flex-row align-items-center",children:(0,r.jsx)(c.KB,{children:(0,r.jsx)(c.rb,{className:"justify-content-center",children:(0,r.jsxs)(c.b7,{md:6,children:[(0,r.jsxs)("div",{className:"clearfix",children:[(0,r.jsx)("h1",{className:"float-start display-3 me-4",children:"404"}),(0,r.jsx)("h4",{className:"pt-3",children:"not found."}),(0,r.jsx)("p",{className:"text-medium-emphasis float-start",children:"\ud574\ub2f9 \ud398\uc774\uc9c0\ub97c \ucc3e\uc744 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4. \uad00\ub9ac\uc790\uc5d0\uac8c \ubb38\uc758\ud558\uc138\uc694."})]}),(0,r.jsxs)(c.YR,{className:"input-prepend",children:[(0,r.jsx)(c.wV,{children:(0,r.jsx)(a.Z,{icon:n})}),(0,r.jsx)(c.jO,{type:"text",placeholder:"\ubb34\uc5c7\uc744 \ucc3e\uc73c\uc2dc\ub098\uc694?"}),(0,r.jsx)(c.u5,{color:"info",children:"\ucc3e\uae30"})]})]})})})})}}}]);
|
||||||
1
console/static/js/617.ef9b57b1.chunk.js
Normal file
1
console/static/js/617.ef9b57b1.chunk.js
Normal file
File diff suppressed because one or more lines are too long
1
console/static/js/664.556c2c72.chunk.js
Normal file
1
console/static/js/664.556c2c72.chunk.js
Normal file
File diff suppressed because one or more lines are too long
1
console/static/js/691.b7fcc27d.chunk.js
Normal file
1
console/static/js/691.b7fcc27d.chunk.js
Normal file
File diff suppressed because one or more lines are too long
2
console/static/js/811.8f231000.chunk.js
Normal file
2
console/static/js/811.8f231000.chunk.js
Normal file
File diff suppressed because one or more lines are too long
1
console/static/js/811.8f231000.chunk.js.LICENSE.txt
Normal file
1
console/static/js/811.8f231000.chunk.js.LICENSE.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
/*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/facebook/regenerator/blob/main/LICENSE */
|
||||||
2
console/static/js/998.7113153c.chunk.js
Normal file
2
console/static/js/998.7113153c.chunk.js
Normal file
File diff suppressed because one or more lines are too long
14
console/static/js/998.7113153c.chunk.js.LICENSE.txt
Normal file
14
console/static/js/998.7113153c.chunk.js.LICENSE.txt
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
/*!
|
||||||
|
Copyright (c) 2018 Jed Watson.
|
||||||
|
Licensed under the MIT License (MIT), see
|
||||||
|
http://jedwatson.github.io/classnames
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** @license React v16.13.1
|
||||||
|
* react-is.development.js
|
||||||
|
*
|
||||||
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
2
console/static/js/main.2ea4d7d1.js
Normal file
2
console/static/js/main.2ea4d7d1.js
Normal file
File diff suppressed because one or more lines are too long
130
console/static/js/main.2ea4d7d1.js.LICENSE.txt
Normal file
130
console/static/js/main.2ea4d7d1.js.LICENSE.txt
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
/*! *****************************************************************************
|
||||||
|
Copyright (c) Microsoft Corporation.
|
||||||
|
|
||||||
|
Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
purpose with or without fee is hereby granted.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||||
|
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||||
|
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||||
|
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||||
|
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||||
|
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||||
|
PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
***************************************************************************** */
|
||||||
|
|
||||||
|
/*! @azure/msal-browser v2.31.0 2022-11-07 */
|
||||||
|
|
||||||
|
/*! @azure/msal-common v8.0.0 2022-11-07 */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @license React
|
||||||
|
* react-dom.production.min.js
|
||||||
|
*
|
||||||
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @license React
|
||||||
|
* react-is.production.min.js
|
||||||
|
*
|
||||||
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @license React
|
||||||
|
* react-jsx-runtime.production.min.js
|
||||||
|
*
|
||||||
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @license React
|
||||||
|
* react.production.min.js
|
||||||
|
*
|
||||||
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @license React
|
||||||
|
* scheduler.production.min.js
|
||||||
|
*
|
||||||
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @license React
|
||||||
|
* use-sync-external-store-shim.production.min.js
|
||||||
|
*
|
||||||
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @license React
|
||||||
|
* use-sync-external-store-shim/with-selector.production.min.js
|
||||||
|
*
|
||||||
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @remix-run/router v1.0.3
|
||||||
|
*
|
||||||
|
* Copyright (c) Remix Software Inc.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE.md file in the root directory of this source tree.
|
||||||
|
*
|
||||||
|
* @license MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* React Router DOM v6.4.3
|
||||||
|
*
|
||||||
|
* Copyright (c) Remix Software Inc.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE.md file in the root directory of this source tree.
|
||||||
|
*
|
||||||
|
* @license MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* React Router v6.4.3
|
||||||
|
*
|
||||||
|
* Copyright (c) Remix Software Inc.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE.md file in the root directory of this source tree.
|
||||||
|
*
|
||||||
|
* @license MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** @license React v16.13.1
|
||||||
|
* react-is.production.min.js
|
||||||
|
*
|
||||||
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
472
core/api.go
Normal file
472
core/api.go
Normal file
@ -0,0 +1,472 @@
|
|||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"repositories.action2quare.com/ayo/go-ayo/common"
|
||||||
|
"repositories.action2quare.com/ayo/go-ayo/logger"
|
||||||
|
|
||||||
|
"go.mongodb.org/mongo-driver/bson"
|
||||||
|
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||||
|
"go.mongodb.org/mongo-driver/mongo/options"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (caller apiCaller) isGlobalAdmin() bool {
|
||||||
|
if *noauth {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
email, ok := caller.userinfo["email"]
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := caller.admins[email.(string)]; ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (caller apiCaller) writeAccessableServices(w http.ResponseWriter) {
|
||||||
|
services, editable := caller.getAccessableServices()
|
||||||
|
for _, r := range editable {
|
||||||
|
w.Header().Add("MG-X-SERVICE-EDITABLE", r)
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Write([]byte("{"))
|
||||||
|
start := true
|
||||||
|
for _, v := range services {
|
||||||
|
if !start {
|
||||||
|
w.Write([]byte(","))
|
||||||
|
}
|
||||||
|
w.Write([]byte(fmt.Sprintf(`"%s":`, v.ServiceName)))
|
||||||
|
serptr := atomic.LoadPointer(&v.serviceSerialized)
|
||||||
|
w.Write(*(*[]byte)(serptr))
|
||||||
|
start = false
|
||||||
|
}
|
||||||
|
w.Write([]byte("}"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (caller apiCaller) getAccessableServices() ([]*serviceDescription, []string) {
|
||||||
|
allservices := caller.mg.services.all()
|
||||||
|
v, ok := caller.userinfo["email"]
|
||||||
|
if !ok {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
email := v.(string)
|
||||||
|
_, admin := caller.admins[email]
|
||||||
|
|
||||||
|
var output []*serviceDescription
|
||||||
|
var editable []string
|
||||||
|
for _, desc := range allservices {
|
||||||
|
if admin {
|
||||||
|
output = append(output, desc)
|
||||||
|
editable = append(editable, desc.ServiceName)
|
||||||
|
} else if desc.isValidAPIUser("*", email) {
|
||||||
|
output = append(output, desc)
|
||||||
|
if desc.isValidAPIUser("service", email) {
|
||||||
|
editable = append(editable, desc.ServiceName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Slice(output, func(i, j int) bool {
|
||||||
|
return output[i].ServiceName < output[j].ServiceName
|
||||||
|
})
|
||||||
|
|
||||||
|
return output, editable
|
||||||
|
}
|
||||||
|
|
||||||
|
func (caller apiCaller) isValidUser(service any, category string) (valid bool, admin bool) {
|
||||||
|
if *noauth {
|
||||||
|
return true, true
|
||||||
|
}
|
||||||
|
|
||||||
|
v, ok := caller.userinfo["email"]
|
||||||
|
if !ok {
|
||||||
|
logger.Println("isVaidUser failed. email is missing :", caller.userinfo)
|
||||||
|
return false, false
|
||||||
|
}
|
||||||
|
|
||||||
|
email := v.(string)
|
||||||
|
if _, ok := caller.admins[email]; ok {
|
||||||
|
return true, true
|
||||||
|
}
|
||||||
|
|
||||||
|
svcdesc := caller.mg.services.get(service)
|
||||||
|
if svcdesc == nil {
|
||||||
|
logger.Println("isVaidUser failed. service is missing :", service)
|
||||||
|
return false, false
|
||||||
|
}
|
||||||
|
|
||||||
|
return svcdesc.isValidAPIUser(category, email), false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (caller apiCaller) whitelistAPI(w http.ResponseWriter, r *http.Request) error {
|
||||||
|
mg := caller.mg
|
||||||
|
queryvals := r.URL.Query()
|
||||||
|
if r.Method == "GET" {
|
||||||
|
service := queryvals.Get("service")
|
||||||
|
|
||||||
|
if valid, _ := caller.isValidUser(service, "whitelist"); !valid {
|
||||||
|
logger.Println("whitelistAPI failed. not vaild user :", r.Method, caller.userinfo)
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(service) > 0 {
|
||||||
|
|
||||||
|
all, err := mg.mongoClient.FindAll(CollectionWhitelist, bson.M{
|
||||||
|
"service": service,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(all) > 0 {
|
||||||
|
allraw, _ := json.Marshal(all)
|
||||||
|
w.Write(allraw)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger.Println("service param is missing")
|
||||||
|
}
|
||||||
|
} else if r.Method == "PUT" {
|
||||||
|
body, _ := io.ReadAll(r.Body)
|
||||||
|
var member whitelistmember
|
||||||
|
if err := json.Unmarshal(body, &member); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if valid, _ := caller.isValidUser(member.Service, "whitelist"); !valid {
|
||||||
|
logger.Println("whitelistAPI failed. not vaild user :", r.Method, caller.userinfo)
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
member.Expired = 0
|
||||||
|
|
||||||
|
_, _, err := mg.mongoClient.Update(CollectionWhitelist, bson.M{
|
||||||
|
"_id": primitive.NewObjectID(),
|
||||||
|
}, bson.M{
|
||||||
|
"$set": &member,
|
||||||
|
}, options.Update().SetUpsert(true))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else if r.Method == "DELETE" {
|
||||||
|
id := queryvals.Get("id")
|
||||||
|
if len(id) == 0 {
|
||||||
|
return errors.New("id param is missing")
|
||||||
|
}
|
||||||
|
idobj, err := primitive.ObjectIDFromHex(id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, _, err = mg.mongoClient.Update(CollectionWhitelist, bson.M{
|
||||||
|
"_id": idobj,
|
||||||
|
}, bson.M{
|
||||||
|
"$currentDate": bson.M{
|
||||||
|
"_ts": bson.M{"$type": "date"},
|
||||||
|
},
|
||||||
|
}, options.Update().SetUpsert(false))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (caller apiCaller) serviceAPI(w http.ResponseWriter, r *http.Request) error {
|
||||||
|
mg := caller.mg
|
||||||
|
queryvals := r.URL.Query()
|
||||||
|
if r.Method == "GET" {
|
||||||
|
name := queryvals.Get("name")
|
||||||
|
if len(name) > 0 {
|
||||||
|
if valid, _ := caller.isValidUser(name, "*"); !valid {
|
||||||
|
logger.Println("serviceAPI failed. not vaild user :", r.Method, caller.userinfo)
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if valid, admin := caller.isValidUser(name, "service"); valid || admin {
|
||||||
|
w.Header().Add("MG-X-SERVICE-EDITABLE", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
serptr := atomic.LoadPointer(&mg.services.get(name).serviceSerialized)
|
||||||
|
w.Write(*(*[]byte)(serptr))
|
||||||
|
} else {
|
||||||
|
caller.writeAccessableServices(w)
|
||||||
|
}
|
||||||
|
} else if r.Method == "POST" {
|
||||||
|
body, _ := io.ReadAll(r.Body)
|
||||||
|
var service serviceDescription
|
||||||
|
if err := json.Unmarshal(body, &service); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if service.Id.IsZero() {
|
||||||
|
if caller.isGlobalAdmin() {
|
||||||
|
service.Id = primitive.NewObjectID()
|
||||||
|
} else {
|
||||||
|
logger.Println("serviceAPI failed. not vaild user :", r.Method, caller.userinfo)
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
} else if valid, _ := caller.isValidUser(service.Id, "service"); !valid {
|
||||||
|
logger.Println("serviceAPI failed. not vaild user :", r.Method, caller.userinfo)
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
filter := bson.M{"_id": service.Id}
|
||||||
|
success, _, err := mg.mongoClient.Update(CollectionService, filter, bson.M{
|
||||||
|
"$set": &service,
|
||||||
|
}, options.Update().SetUpsert(true))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !success {
|
||||||
|
logger.Println("serviceAPI failed. not vaild user :", caller.userinfo)
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (caller apiCaller) accountAPI(w http.ResponseWriter, r *http.Request) error {
|
||||||
|
mg := caller.mg
|
||||||
|
queryvals := r.URL.Query()
|
||||||
|
if r.Method == "GET" {
|
||||||
|
service := queryvals.Get("service")
|
||||||
|
if len(service) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if valid, _ := caller.isValidUser(service, "account"); !valid {
|
||||||
|
logger.Println("accountAPI failed. not vaild user :", r.Method, caller.userinfo)
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var accdoc primitive.M
|
||||||
|
if v := queryvals.Get("accid"); len(v) == 0 {
|
||||||
|
email := queryvals.Get("email")
|
||||||
|
platform := queryvals.Get("platform")
|
||||||
|
if len(email) == 0 || len(platform) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
found, err := mg.mongoClient.FindOne(CollectionLink, bson.M{
|
||||||
|
"email": email,
|
||||||
|
"platform": platform,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if found == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if idobj, ok := found["_id"]; ok {
|
||||||
|
svcdoc, err := mg.mongoClient.FindOne(common.CollectionName(service), bson.M{
|
||||||
|
"_id": idobj,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if svcdoc != nil {
|
||||||
|
found["accid"] = svcdoc["accid"]
|
||||||
|
}
|
||||||
|
|
||||||
|
accdoc = found
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
accid, err := primitive.ObjectIDFromHex(v)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
svcdoc, err := mg.mongoClient.FindOne(common.CollectionName(service), bson.M{
|
||||||
|
"accid": accid,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
found, err := mg.mongoClient.FindOne(CollectionLink, bson.M{
|
||||||
|
"_id": svcdoc["_id"],
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if found != nil {
|
||||||
|
found["accid"] = accid
|
||||||
|
}
|
||||||
|
accdoc = found
|
||||||
|
}
|
||||||
|
|
||||||
|
if accdoc != nil {
|
||||||
|
accdoc["code"] = service
|
||||||
|
delete(accdoc, "uid")
|
||||||
|
delete(accdoc, "_id")
|
||||||
|
|
||||||
|
var bi blockinfo
|
||||||
|
if err := mg.mongoClient.FindOneAs(CollectionBlock, bson.M{
|
||||||
|
"code": service,
|
||||||
|
"accid": accdoc["accid"],
|
||||||
|
}, &bi); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !bi.Start.Time().IsZero() && bi.End.Time().After(time.Now().UTC()) {
|
||||||
|
accdoc["blocked"] = bi
|
||||||
|
}
|
||||||
|
return json.NewEncoder(w).Encode(accdoc)
|
||||||
|
}
|
||||||
|
} else if r.Method == "POST" {
|
||||||
|
var account struct {
|
||||||
|
Code string
|
||||||
|
Accid string
|
||||||
|
Blocked blockinfo
|
||||||
|
}
|
||||||
|
|
||||||
|
body, _ := io.ReadAll(r.Body)
|
||||||
|
if err := json.Unmarshal(body, &account); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
accid, _ := primitive.ObjectIDFromHex(account.Accid)
|
||||||
|
if !account.Blocked.Start.Time().IsZero() && account.Blocked.Start.Time().After(time.Now().UTC()) {
|
||||||
|
if _, _, err := mg.mongoClient.Update(CollectionBlock, bson.M{
|
||||||
|
"code": account.Code,
|
||||||
|
"accid": accid,
|
||||||
|
}, bson.M{
|
||||||
|
"$set": account.Blocked,
|
||||||
|
}, options.Update().SetUpsert(true)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var errApiTokenMissing = errors.New("mg-x-api-token is missing")
|
||||||
|
|
||||||
|
func (caller apiCaller) configAPI(w http.ResponseWriter, r *http.Request) error {
|
||||||
|
mg := caller.mg
|
||||||
|
apitoken := r.Header.Get("MG-X-API-TOKEN")
|
||||||
|
if len(apitoken) == 0 {
|
||||||
|
return errApiTokenMissing
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, exists := mg.apiTokenToService.get(apitoken); !exists {
|
||||||
|
return fmt.Errorf("mg-x-api-token is not valid : %s", apitoken)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var noauth = flag.Bool("noauth", false, "")
|
||||||
|
|
||||||
|
type apiCaller struct {
|
||||||
|
userinfo map[string]any
|
||||||
|
admins map[string]bool
|
||||||
|
mg *Maingate
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mg *Maingate) api(w http.ResponseWriter, r *http.Request) {
|
||||||
|
defer func() {
|
||||||
|
s := recover()
|
||||||
|
if s != nil {
|
||||||
|
logger.Error(s)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
io.Copy(io.Discard, r.Body)
|
||||||
|
r.Body.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
|
var userinfo map[string]any
|
||||||
|
|
||||||
|
if !*noauth {
|
||||||
|
authheader := r.Header.Get("Authorization")
|
||||||
|
if len(authheader) == 0 {
|
||||||
|
logger.Println("Authorization header is not valid :", authheader)
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
req, _ := http.NewRequest("GET", "https://graph.microsoft.com/oidc/userinfo", nil)
|
||||||
|
req.Header.Add("Authorization", authheader)
|
||||||
|
client := &http.Client{}
|
||||||
|
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
logger.Println("graph microsoft api call failed :", err)
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
raw, _ := io.ReadAll(resp.Body)
|
||||||
|
if err = json.Unmarshal(raw, &userinfo); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, expired := userinfo["error"]; expired {
|
||||||
|
w.WriteHeader(http.StatusUnauthorized)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ptr := atomic.LoadPointer(&mg.admins)
|
||||||
|
adminsptr := (*globalAdmins)(ptr)
|
||||||
|
|
||||||
|
if adminsptr.modtime != common.ConfigModTime() {
|
||||||
|
var config globalAdmins
|
||||||
|
if err := common.LoadConfig(&config); err == nil {
|
||||||
|
config.parse()
|
||||||
|
adminsptr = &config
|
||||||
|
atomic.StorePointer(&mg.admins, unsafe.Pointer(adminsptr))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Println("api call :", r.URL.Path, r.Method, r.URL.Query(), userinfo)
|
||||||
|
caller := apiCaller{
|
||||||
|
userinfo: userinfo,
|
||||||
|
admins: adminsptr.emails,
|
||||||
|
mg: mg,
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
if strings.HasSuffix(r.URL.Path, "/service") {
|
||||||
|
err = caller.serviceAPI(w, r)
|
||||||
|
} else if strings.HasSuffix(r.URL.Path, "/whitelist") {
|
||||||
|
err = caller.whitelistAPI(w, r)
|
||||||
|
} else if strings.HasSuffix(r.URL.Path, "/config") {
|
||||||
|
err = caller.configAPI(w, r)
|
||||||
|
} else if strings.HasSuffix(r.URL.Path, "/account") {
|
||||||
|
err = caller.accountAPI(w, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
logger.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
959
core/maingate.go
Normal file
959
core/maingate.go
Normal file
@ -0,0 +1,959 @@
|
|||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/rsa"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"html/template"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"math/big"
|
||||||
|
"math/rand"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"repositories.action2quare.com/ayo/go-ayo/common"
|
||||||
|
"repositories.action2quare.com/ayo/go-ayo/logger"
|
||||||
|
|
||||||
|
"github.com/golang-jwt/jwt"
|
||||||
|
"go.mongodb.org/mongo-driver/bson"
|
||||||
|
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||||
|
"go.mongodb.org/mongo-driver/mongo/options"
|
||||||
|
|
||||||
|
firebase "firebase.google.com/go"
|
||||||
|
"firebase.google.com/go/auth"
|
||||||
|
|
||||||
|
"google.golang.org/api/option"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
CollectionLink = common.CollectionName("link")
|
||||||
|
CollectionAuth = common.CollectionName("auth")
|
||||||
|
CollectionWhitelist = common.CollectionName("whitelist")
|
||||||
|
CollectionService = common.CollectionName("service")
|
||||||
|
CollectionBlock = common.CollectionName("block")
|
||||||
|
CollectionPlatformLoginToken = common.CollectionName("platform_login_token") //-- 각 플랫폼에 로그인 및 권한 받아오는 과정에 사용하는 Key
|
||||||
|
CollectionUserToken = common.CollectionName("usertoken")
|
||||||
|
CollectionGamepotUserInfo = common.CollectionName("gamepot_userinfo") //-- 클라로부터 수집된 gamepot 정보 - server to server로 유효성이 검증되진 않았지만 수집은 한다.
|
||||||
|
CollectionFirebaseUserInfo = common.CollectionName("firebase_userinfo") //-- Firebase UserInfo
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
AuthPlatformFirebaseAuth = "firebase"
|
||||||
|
AuthPlatformGamepot = "gamepot"
|
||||||
|
AuthPlatformGoogle = "google"
|
||||||
|
AuthPlatformMicrosoft = "microsoft"
|
||||||
|
AuthPlatformApple = "apple"
|
||||||
|
AuthPlatformTwitter = "twitter"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
sessionTTL = time.Hour
|
||||||
|
sessionTTLDev = 1 * time.Hour
|
||||||
|
)
|
||||||
|
|
||||||
|
func SessionTTL() time.Duration {
|
||||||
|
if *common.Devflag {
|
||||||
|
return sessionTTLDev
|
||||||
|
}
|
||||||
|
|
||||||
|
return sessionTTL
|
||||||
|
}
|
||||||
|
|
||||||
|
type mongoAuthCell struct {
|
||||||
|
src *common.Authinfo
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
if *common.Devflag {
|
||||||
|
hostname, _ := os.Hostname()
|
||||||
|
CollectionLink = common.CollectionName(fmt.Sprintf("%s-%s", hostname, string(CollectionLink)))
|
||||||
|
CollectionAuth = common.CollectionName(fmt.Sprintf("%s-%s", hostname, string(CollectionAuth)))
|
||||||
|
CollectionWhitelist = common.CollectionName(fmt.Sprintf("%s-%s", hostname, string(CollectionWhitelist)))
|
||||||
|
CollectionService = common.CollectionName(fmt.Sprintf("%s-%s", hostname, string(CollectionService)))
|
||||||
|
CollectionBlock = common.CollectionName(fmt.Sprintf("%s-%s", hostname, string(CollectionBlock)))
|
||||||
|
CollectionPlatformLoginToken = common.CollectionName(fmt.Sprintf("%s-%s", hostname, string(CollectionPlatformLoginToken)))
|
||||||
|
CollectionUserToken = common.CollectionName(fmt.Sprintf("%s-%s", hostname, string(CollectionUserToken)))
|
||||||
|
CollectionGamepotUserInfo = common.CollectionName(fmt.Sprintf("%s-%s", hostname, string(CollectionGamepotUserInfo)))
|
||||||
|
CollectionFirebaseUserInfo = common.CollectionName(fmt.Sprintf("%s-%s", hostname, string(CollectionFirebaseUserInfo)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ac *mongoAuthCell) ToAuthinfo() *common.Authinfo {
|
||||||
|
if ac.src == nil {
|
||||||
|
logger.Error("mongoAuthCell ToAuthinfo failed. ac.src is nil")
|
||||||
|
}
|
||||||
|
return ac.src
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ac *mongoAuthCell) ToBytes() []byte {
|
||||||
|
bt, _ := json.Marshal(ac.src)
|
||||||
|
return bt
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeAuthCollection(mongoClient common.MongoClient, sessionTTL time.Duration) *common.AuthCollection {
|
||||||
|
authcoll := common.MakeAuthCollection(sessionTTL)
|
||||||
|
authcoll.SessionRemoved = func(sk string) {
|
||||||
|
skid, _ := primitive.ObjectIDFromHex(sk)
|
||||||
|
mongoClient.Delete(CollectionAuth, bson.M{
|
||||||
|
"sk": skid,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
authcoll.QuerySession = func(sk string, token string) common.AuthinfoCell {
|
||||||
|
skid, _ := primitive.ObjectIDFromHex(sk)
|
||||||
|
var outcell mongoAuthCell
|
||||||
|
err := mongoClient.FindOneAs(CollectionAuth, bson.M{
|
||||||
|
"sk": skid,
|
||||||
|
}, &outcell.src, options.FindOne().SetHint("skonly"))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("QuerySession failed :", err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if outcell.src == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return &outcell
|
||||||
|
}
|
||||||
|
|
||||||
|
return authcoll
|
||||||
|
}
|
||||||
|
|
||||||
|
type apiTokenMap struct {
|
||||||
|
sync.Mutex
|
||||||
|
tokenToService map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tm *apiTokenMap) add(token string, serviceCode string) {
|
||||||
|
tm.Lock()
|
||||||
|
defer tm.Unlock()
|
||||||
|
|
||||||
|
tm.tokenToService[token] = serviceCode
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tm *apiTokenMap) remove(token string) {
|
||||||
|
tm.Lock()
|
||||||
|
defer tm.Unlock()
|
||||||
|
|
||||||
|
delete(tm.tokenToService, token)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tm *apiTokenMap) get(token string) (code string, exists bool) {
|
||||||
|
tm.Lock()
|
||||||
|
defer tm.Unlock()
|
||||||
|
|
||||||
|
code, exists = tm.tokenToService[token]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type maingateConfig struct {
|
||||||
|
Mongo string `json:"maingate_mongodb_url"`
|
||||||
|
SessionTTL int64 `json:"maingate_session_ttl"`
|
||||||
|
Autologin_ttl int64 `json:"autologin_ttl"`
|
||||||
|
RedirectBaseUrl string `json:"redirect_base_url"`
|
||||||
|
GoogleClientId string `json:"google_client_id"`
|
||||||
|
GoogleClientSecret string `json:"google_client_secret"`
|
||||||
|
TwitterOAuthKey string `json:"twitter_oauth_key"`
|
||||||
|
TwitterOAuthSecret string `json:"twitter_oauth_secret"`
|
||||||
|
TwitterCustomerKey string `json:"twitter_customer_key"`
|
||||||
|
TwitterCustomerSecret string `json:"twitter_customer_secret"`
|
||||||
|
AppleCientId string `json:"apple_client_id"`
|
||||||
|
ApplePrivateKey string `json:"apple_privatekey"`
|
||||||
|
AppleServiceId string `json:"apple_service_id"`
|
||||||
|
AppleTeamId string `json:"apple_team_id"`
|
||||||
|
AppleKeyId string `json:"apple_key_id"`
|
||||||
|
MicrosoftClientId string `json:"microsoft_client_id"`
|
||||||
|
MicrosoftClientSecret string `json:"microsoft_client_secret"`
|
||||||
|
GamepotProjectId string `json:"gamepot_project_id"`
|
||||||
|
GamepotLoginCheckAPIURL string `json:"gamepot_logincheckapi_url"`
|
||||||
|
FirebaseAdminSDKCredentialFile string `json:"firebase_admin_sdk_credentialfile"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type globalAdmins struct {
|
||||||
|
Admins []string `json:"maingate_global_admins"`
|
||||||
|
emails map[string]bool
|
||||||
|
modtime time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ga *globalAdmins) parse() {
|
||||||
|
parsed := make(map[string]bool)
|
||||||
|
for _, admin := range ga.Admins {
|
||||||
|
parsed[admin] = true
|
||||||
|
}
|
||||||
|
ga.emails = parsed
|
||||||
|
ga.modtime = common.ConfigModTime()
|
||||||
|
}
|
||||||
|
|
||||||
|
type servicelist struct {
|
||||||
|
services unsafe.Pointer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sl *servicelist) init(total []*serviceDescription) error {
|
||||||
|
next := make(map[string]*serviceDescription)
|
||||||
|
for _, service := range total {
|
||||||
|
next[service.ServiceName] = service
|
||||||
|
}
|
||||||
|
|
||||||
|
atomic.StorePointer(&sl.services, unsafe.Pointer(&next))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sl *servicelist) add(s *serviceDescription) {
|
||||||
|
ptr := atomic.LoadPointer(&sl.services)
|
||||||
|
src := (*map[string]*serviceDescription)(ptr)
|
||||||
|
|
||||||
|
next := map[string]*serviceDescription{}
|
||||||
|
for k, v := range *src {
|
||||||
|
next[k] = v
|
||||||
|
}
|
||||||
|
next[s.ServiceName] = s
|
||||||
|
atomic.StorePointer(&sl.services, unsafe.Pointer(&next))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sl *servicelist) get(sn any) *serviceDescription {
|
||||||
|
ptr := atomic.LoadPointer(&sl.services)
|
||||||
|
src := *(*map[string]*serviceDescription)(ptr)
|
||||||
|
|
||||||
|
switch sn := sn.(type) {
|
||||||
|
case string:
|
||||||
|
return src[sn]
|
||||||
|
|
||||||
|
case primitive.ObjectID:
|
||||||
|
for _, desc := range src {
|
||||||
|
if desc.Id == sn {
|
||||||
|
return desc
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sl *servicelist) all() map[string]*serviceDescription {
|
||||||
|
ptr := atomic.LoadPointer(&sl.services)
|
||||||
|
src := (*map[string]*serviceDescription)(ptr)
|
||||||
|
|
||||||
|
next := map[string]*serviceDescription{}
|
||||||
|
for k, v := range *src {
|
||||||
|
next[k] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
return next
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sl *servicelist) remove(uid primitive.ObjectID) (out *serviceDescription) {
|
||||||
|
ptr := atomic.LoadPointer(&sl.services)
|
||||||
|
src := (*map[string]*serviceDescription)(ptr)
|
||||||
|
|
||||||
|
next := map[string]*serviceDescription{}
|
||||||
|
var targetkey string
|
||||||
|
out = nil
|
||||||
|
for k, v := range *src {
|
||||||
|
next[k] = v
|
||||||
|
if v.Id == uid {
|
||||||
|
targetkey = k
|
||||||
|
out = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
delete(next, targetkey)
|
||||||
|
atomic.StorePointer(&sl.services, unsafe.Pointer(&next))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Maingate :
|
||||||
|
type Maingate struct {
|
||||||
|
maingateConfig
|
||||||
|
|
||||||
|
mongoClient common.MongoClient
|
||||||
|
|
||||||
|
auths *common.AuthCollection
|
||||||
|
services servicelist
|
||||||
|
admins unsafe.Pointer
|
||||||
|
apiTokenToService apiTokenMap
|
||||||
|
tokenEndpoints map[string]string
|
||||||
|
authorizationEndpoints map[string]string
|
||||||
|
userinfoEndpoint map[string]string
|
||||||
|
jwksUri map[string]string
|
||||||
|
webTemplate map[string]*template.Template
|
||||||
|
firebaseAppClient *auth.Client
|
||||||
|
firebaseAppContext context.Context
|
||||||
|
}
|
||||||
|
|
||||||
|
// New :
|
||||||
|
func New(ctx context.Context) (*Maingate, error) {
|
||||||
|
var config maingateConfig
|
||||||
|
if err := common.LoadConfig(&config); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var admins globalAdmins
|
||||||
|
if err := common.LoadConfig(&admins); err == nil {
|
||||||
|
admins.parse()
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.SessionTTL == 0 {
|
||||||
|
config.SessionTTL = 3600
|
||||||
|
}
|
||||||
|
|
||||||
|
mg := Maingate{
|
||||||
|
maingateConfig: config,
|
||||||
|
services: servicelist{},
|
||||||
|
admins: unsafe.Pointer(&admins),
|
||||||
|
apiTokenToService: apiTokenMap{
|
||||||
|
tokenToService: make(map[string]string),
|
||||||
|
},
|
||||||
|
tokenEndpoints: make(map[string]string),
|
||||||
|
authorizationEndpoints: make(map[string]string),
|
||||||
|
userinfoEndpoint: make(map[string]string),
|
||||||
|
jwksUri: make(map[string]string),
|
||||||
|
}
|
||||||
|
|
||||||
|
err := mg.prepare(ctx)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("mg.prepare() failed :", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
opt := option.WithCredentialsFile(mg.FirebaseAdminSDKCredentialFile)
|
||||||
|
firebaseApp, err := firebase.NewApp(context.Background(), nil, opt)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("firebase admin error initializing app failed :", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
mg.firebaseAppContext = ctx
|
||||||
|
mg.firebaseAppClient, err = firebaseApp.Auth(mg.firebaseAppContext)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("error getting Auth client: %v\n", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &mg, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mg *Maingate) Destructor() {
|
||||||
|
logger.Println("maingate.Destructor")
|
||||||
|
mg.mongoClient.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mg *Maingate) discoverOpenIdConfiguration(name string, url string) error {
|
||||||
|
// microsoft open-id configuration
|
||||||
|
discover, err := http.Get(url)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer discover.Body.Close()
|
||||||
|
body, err := io.ReadAll(discover.Body)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var endpoints map[string]any
|
||||||
|
if err := json.Unmarshal(body, &endpoints); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if tokenEndpoint, ok := endpoints["token_endpoint"].(string); ok {
|
||||||
|
mg.tokenEndpoints[name] = tokenEndpoint
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("token_endpoint is missing. %s %s", name, url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if authorizationEndpoint, ok := endpoints["authorization_endpoint"].(string); ok {
|
||||||
|
mg.authorizationEndpoints[name] = authorizationEndpoint
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("authorization_endpoint is missing. %s %s", name, url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if userinfoEndpoint, ok := endpoints["userinfo_endpoint"].(string); ok {
|
||||||
|
mg.userinfoEndpoint[name] = userinfoEndpoint
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("userinfo_endpoint is missing. %s %s", name, url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if jwksUri, ok := endpoints["jwks_uri"].(string); ok {
|
||||||
|
mg.jwksUri[name] = jwksUri
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("jwks_uri is missing. %s %s", name, url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mg *Maingate) prepare(context context.Context) (err error) {
|
||||||
|
if err := mg.discoverOpenIdConfiguration(AuthPlatformMicrosoft, "https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := mg.discoverOpenIdConfiguration("google", "https://accounts.google.com/.well-known/openid-configuration"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
mg.webTemplate = make(map[string]*template.Template)
|
||||||
|
mg.webTemplate[AuthPlatformGamepot] = template.Must(template.ParseFiles("www/gamepot.html"))
|
||||||
|
|
||||||
|
// redis에서 env를 가져온 후에
|
||||||
|
mg.mongoClient, err = common.NewMongoClient(context, mg.Mongo, "maingate")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = mg.mongoClient.MakeUniqueIndices(CollectionAuth, map[string]bson.D{
|
||||||
|
"skonly": {{Key: "sk", Value: 1}},
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = mg.mongoClient.MakeUniqueIndices(CollectionLink, map[string]bson.D{
|
||||||
|
"platformuid": {{Key: "platform", Value: 1}, {Key: "uid", Value: 1}},
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = mg.mongoClient.MakeUniqueIndices(CollectionLink, map[string]bson.D{
|
||||||
|
"emailplatform": {{Key: "email", Value: 1}, {Key: "platform", Value: 1}},
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = mg.mongoClient.MakeIndices(CollectionWhitelist, map[string]bson.D{
|
||||||
|
"service": {{Key: "service", Value: 1}},
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = mg.mongoClient.MakeExpireIndex(CollectionWhitelist, 10); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = mg.mongoClient.MakeExpireIndex(CollectionAuth, int32(mg.SessionTTL+300)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = mg.mongoClient.MakeUniqueIndices(CollectionBlock, map[string]bson.D{
|
||||||
|
"codeaccid": {{Key: "code", Value: 1}, {Key: "accid", Value: 1}},
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = mg.mongoClient.MakeExpireIndex(CollectionBlock, int32(3)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = mg.mongoClient.MakeUniqueIndices(CollectionPlatformLoginToken, map[string]bson.D{
|
||||||
|
"platformauthtoken": {{Key: "platform", Value: 1}, {Key: "key", Value: 1}},
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = mg.mongoClient.MakeExpireIndex(CollectionPlatformLoginToken, int32(mg.SessionTTL+300)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = mg.mongoClient.MakeUniqueIndices(CollectionUserToken, map[string]bson.D{
|
||||||
|
"platformusertoken": {{Key: "platform", Value: 1}, {Key: "userid", Value: 1}},
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = mg.mongoClient.MakeUniqueIndices(CollectionGamepotUserInfo, map[string]bson.D{
|
||||||
|
"gamepotuserid": {{Key: "gamepotuserid", Value: 1}},
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = mg.mongoClient.MakeUniqueIndices(CollectionFirebaseUserInfo, map[string]bson.D{
|
||||||
|
"firebaseuserid": {{Key: "firebaseuserid", Value: 1}},
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
mg.auths = makeAuthCollection(mg.mongoClient, time.Duration(mg.SessionTTL*int64(time.Second)))
|
||||||
|
|
||||||
|
go watchAuthCollection(context, mg.auths, mg.mongoClient)
|
||||||
|
go mg.watchWhitelistCollection(context)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func whitelistKey(email string) string {
|
||||||
|
if strings.HasPrefix(email, "*@") {
|
||||||
|
// 도메인 전체 허용
|
||||||
|
return email[2:]
|
||||||
|
}
|
||||||
|
|
||||||
|
return email
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mg *Maingate) RegisterHandlers(ctx context.Context, serveMux *http.ServeMux, prefix string) error {
|
||||||
|
var allServices []*serviceDescription
|
||||||
|
logger.Println(CollectionService)
|
||||||
|
if err := mg.mongoClient.FindAllAs(CollectionService, bson.M{}, &allServices, options.Find().SetReturnKey(false)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, service := range allServices {
|
||||||
|
if err := service.prepare(mg); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Println("RegisterHandlers...")
|
||||||
|
|
||||||
|
mg.services.init(allServices)
|
||||||
|
for _, service := range allServices {
|
||||||
|
if service.Closed {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Println("ServiceCode:", service.ServiceCode)
|
||||||
|
serveMux.Handle(common.MakeHttpHandlerPattern(prefix, service.ServiceCode, "/"), service)
|
||||||
|
}
|
||||||
|
|
||||||
|
serveMux.HandleFunc(common.MakeHttpHandlerPattern(prefix, "api", "/"), mg.api)
|
||||||
|
serveMux.HandleFunc(common.MakeHttpHandlerPattern(prefix, "query", "/"), mg.query)
|
||||||
|
|
||||||
|
configraw, _ := json.Marshal(mg.maingateConfig)
|
||||||
|
serveMux.HandleFunc(common.MakeHttpHandlerPattern(prefix, "config"), func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
apitoken := r.Header.Get("MG-X-API-TOKEN")
|
||||||
|
if len(apitoken) == 0 {
|
||||||
|
logger.Println("MG-X-API-TOKEN is missing")
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, exists := mg.apiTokenToService.get(apitoken)
|
||||||
|
if !exists {
|
||||||
|
logger.Println("MG-X-API-TOKEN is invalid :", apitoken)
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Write(configraw)
|
||||||
|
})
|
||||||
|
|
||||||
|
fsx := http.FileServer(http.Dir("./console"))
|
||||||
|
serveMux.Handle(common.MakeHttpHandlerPattern(prefix, "console", "/"), http.StripPrefix("/console/", fsx))
|
||||||
|
|
||||||
|
serveMux.HandleFunc(common.MakeHttpHandlerPattern(prefix, "request_login_url", AuthPlatformGoogle), mg.platform_google_get_login_url)
|
||||||
|
serveMux.HandleFunc(common.MakeHttpHandlerPattern(prefix, "authorize", AuthPlatformGoogle), mg.platform_google_authorize)
|
||||||
|
serveMux.HandleFunc(common.MakeHttpHandlerPattern(prefix, "authorize_result", AuthPlatformGoogle), mg.platform_google_authorize_result)
|
||||||
|
|
||||||
|
serveMux.HandleFunc(common.MakeHttpHandlerPattern(prefix, "request_login_url", AuthPlatformMicrosoft), mg.platform_microsoft_get_login_url)
|
||||||
|
serveMux.HandleFunc(common.MakeHttpHandlerPattern(prefix, "authorize", AuthPlatformMicrosoft), mg.platform_microsoft_authorize)
|
||||||
|
serveMux.HandleFunc(common.MakeHttpHandlerPattern(prefix, "authorize_result", AuthPlatformMicrosoft), mg.platform_microsoft_authorize_result)
|
||||||
|
|
||||||
|
serveMux.HandleFunc(common.MakeHttpHandlerPattern(prefix, "request_login_url", AuthPlatformTwitter), mg.platform_twitter_get_login_url)
|
||||||
|
serveMux.HandleFunc(common.MakeHttpHandlerPattern(prefix, "authorize", AuthPlatformTwitter), mg.platform_twitter_authorize)
|
||||||
|
serveMux.HandleFunc(common.MakeHttpHandlerPattern(prefix, "authorize_result", AuthPlatformTwitter), mg.platform_twitter_authorize_result)
|
||||||
|
|
||||||
|
serveMux.HandleFunc(common.MakeHttpHandlerPattern(prefix, "request_login_url", AuthPlatformApple), mg.platform_apple_get_login_url)
|
||||||
|
serveMux.HandleFunc(common.MakeHttpHandlerPattern(prefix, "authorize", AuthPlatformApple), mg.platform_apple_authorize)
|
||||||
|
serveMux.HandleFunc(common.MakeHttpHandlerPattern(prefix, "authorize_result", AuthPlatformApple), mg.platform_apple_authorize_result)
|
||||||
|
|
||||||
|
serveMux.HandleFunc(common.MakeHttpHandlerPattern(prefix, "request_login_url", AuthPlatformGamepot), mg.platform_gamepot_get_login_url)
|
||||||
|
serveMux.HandleFunc(common.MakeHttpHandlerPattern(prefix, "authorize", AuthPlatformGamepot), mg.platform_gamepot_authorize)
|
||||||
|
serveMux.HandleFunc(common.MakeHttpHandlerPattern(prefix, "authorize_sdk", AuthPlatformGamepot), mg.platform_gamepot_authorize_sdk)
|
||||||
|
|
||||||
|
serveMux.HandleFunc(common.MakeHttpHandlerPattern(prefix, "request_login_url", AuthPlatformFirebaseAuth), mg.platform_firebaseauth_get_login_url)
|
||||||
|
serveMux.HandleFunc(common.MakeHttpHandlerPattern(prefix, "authorize_sdk", AuthPlatformFirebaseAuth), mg.platform_firebaseauth_authorize_sdk)
|
||||||
|
|
||||||
|
go mg.watchServiceCollection(ctx, serveMux, prefix)
|
||||||
|
|
||||||
|
// fsx := http.FileServer(http.Dir("console"))
|
||||||
|
// serveMux.Handle("/console/", http.StripPrefix("/console/", fsx))
|
||||||
|
// logger.Println("console file server open")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mg *Maingate) query(w http.ResponseWriter, r *http.Request) {
|
||||||
|
defer func() {
|
||||||
|
s := recover()
|
||||||
|
if s != nil {
|
||||||
|
logger.Error(s)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
io.Copy(io.Discard, r.Body)
|
||||||
|
r.Body.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
|
queryvals := r.URL.Query()
|
||||||
|
sk := queryvals.Get("sk")
|
||||||
|
|
||||||
|
if len(sk) == 0 {
|
||||||
|
w.WriteHeader(http.StatusUnauthorized)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
info := mg.auths.Find(sk)
|
||||||
|
if info == nil {
|
||||||
|
logger.Println("session key is not valid :", sk)
|
||||||
|
w.WriteHeader(http.StatusUnauthorized)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
apitoken := r.Header.Get("MG-X-API-TOKEN")
|
||||||
|
if len(apitoken) == 0 {
|
||||||
|
logger.Println("MG-X-API-TOKEN is missing")
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
servicecode, exists := mg.apiTokenToService.get(apitoken)
|
||||||
|
if !exists {
|
||||||
|
logger.Println("MG-X-API-TOKEN is invalid :", apitoken)
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if info.ServiceCode != servicecode {
|
||||||
|
logger.Println("session is not for this service :", info.ServiceCode, servicecode)
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
bt, _ := json.Marshal(info)
|
||||||
|
w.Write(bt)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mg *Maingate) GeneratePlatformLoginNonceKey() string {
|
||||||
|
const allowed = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
||||||
|
b := make([]byte, 52)
|
||||||
|
for i := range b {
|
||||||
|
b[i] = allowed[rand.Intn(len(allowed))]
|
||||||
|
}
|
||||||
|
return string(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mg *Maingate) GetUserBrowserInfo(r *http.Request) (string, error) {
|
||||||
|
//=========== 브라우저 검증쪽은 앞으로 빼자
|
||||||
|
host, _, err := net.SplitHostPort(r.RemoteAddr)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("Error: RemoteAddr Split Error :", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cookie, err := r.Cookie("ActionSquareSessionExtraInfo")
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
requestinfo := fmt.Sprintf("%s_%s", cookie.Value, host)
|
||||||
|
return requestinfo, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mg *Maingate) setUserToken(info usertokeninfo) error {
|
||||||
|
|
||||||
|
mg.mongoClient.Delete(CollectionUserToken, bson.M{
|
||||||
|
"platform": info.platform,
|
||||||
|
"userid": info.userid,
|
||||||
|
})
|
||||||
|
_, _, err := mg.mongoClient.Update(CollectionUserToken, bson.M{
|
||||||
|
"_id": primitive.NewObjectID(),
|
||||||
|
}, bson.M{
|
||||||
|
"$setOnInsert": bson.M{
|
||||||
|
"platform": info.platform,
|
||||||
|
"userid": info.userid,
|
||||||
|
"token": info.token,
|
||||||
|
"secret": info.secret,
|
||||||
|
"brinfo": info.brinfo,
|
||||||
|
"lastupdate": time.Now().Unix(),
|
||||||
|
"accesstoken": info.accesstoken,
|
||||||
|
"accesstoken_expire_time": info.accesstoken_expire_time,
|
||||||
|
},
|
||||||
|
}, options.Update().SetUpsert(true))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mg *Maingate) getUserTokenWithCheck(platform string, userid string, brinfo string) (usertokeninfo, error) {
|
||||||
|
|
||||||
|
var info usertokeninfo
|
||||||
|
|
||||||
|
found, err := mg.mongoClient.FindOne(CollectionUserToken, bson.M{
|
||||||
|
"platform": platform,
|
||||||
|
"userid": userid,
|
||||||
|
"brinfo": brinfo,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return info, err
|
||||||
|
}
|
||||||
|
if found == nil {
|
||||||
|
return info, errors.New("user token not found: " + platform + " / " + userid + " / " + brinfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
updatetime, ok := found["lastupdate"].(int64)
|
||||||
|
|
||||||
|
if !ok || time.Now().Unix()-updatetime < mg.maingateConfig.Autologin_ttl {
|
||||||
|
info.platform = platform
|
||||||
|
info.userid = userid
|
||||||
|
info.brinfo = brinfo
|
||||||
|
|
||||||
|
token := found["token"]
|
||||||
|
if token != nil {
|
||||||
|
info.token = token.(string)
|
||||||
|
}
|
||||||
|
|
||||||
|
secret := found["secret"]
|
||||||
|
if secret != nil {
|
||||||
|
info.secret = secret.(string)
|
||||||
|
}
|
||||||
|
|
||||||
|
accesstoken := found["accesstoken"]
|
||||||
|
if accesstoken != nil {
|
||||||
|
info.accesstoken = accesstoken.(string)
|
||||||
|
}
|
||||||
|
|
||||||
|
accesstoken_expire_time := found["accesstoken_expire_time"]
|
||||||
|
if accesstoken_expire_time != nil {
|
||||||
|
info.accesstoken_expire_time = accesstoken_expire_time.(int64)
|
||||||
|
}
|
||||||
|
|
||||||
|
return info, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return info, errors.New("session is expired")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mg *Maingate) updateUserinfo(info usertokeninfo) (bool, string, string) {
|
||||||
|
var success bool
|
||||||
|
var userid, email string
|
||||||
|
success = false
|
||||||
|
userid = ""
|
||||||
|
email = ""
|
||||||
|
|
||||||
|
switch info.platform {
|
||||||
|
case AuthPlatformApple:
|
||||||
|
success, userid, email = mg.platform_apple_getuserinfo(info.token)
|
||||||
|
case AuthPlatformTwitter:
|
||||||
|
success, userid, email = mg.platform_twitter_getuserinfo(info.token, info.secret)
|
||||||
|
case AuthPlatformMicrosoft:
|
||||||
|
success, userid, email = mg.platform_microsoft_getuserinfo(info)
|
||||||
|
case AuthPlatformGoogle:
|
||||||
|
success, userid, email = mg.platform_google_getuserinfo(info)
|
||||||
|
case AuthPlatformGamepot:
|
||||||
|
success, userid, email = mg.platform_gamepot_getuserinfo(info)
|
||||||
|
case AuthPlatformFirebaseAuth:
|
||||||
|
success, userid, email = mg.platform_firebase_getuserinfo(info)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !success {
|
||||||
|
return false, "", ""
|
||||||
|
}
|
||||||
|
|
||||||
|
if info.userid != userid {
|
||||||
|
logger.Error("userinfo / id is not match. :", info.userid, " / ", userid)
|
||||||
|
return false, "", ""
|
||||||
|
}
|
||||||
|
|
||||||
|
mg.setUserToken(info)
|
||||||
|
|
||||||
|
return success, userid, email
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mg *Maingate) getProviderInfo(platform string, uid string) (error, string, string) {
|
||||||
|
provider := ""
|
||||||
|
providerid := ""
|
||||||
|
|
||||||
|
switch platform {
|
||||||
|
case AuthPlatformFirebaseAuth: // Fireabase 통해서 로그인 하는 경우, 원래 제공하는 Google 혹은 Apple쪽 ID와 일치 시킨다
|
||||||
|
found, err := mg.mongoClient.FindOne(CollectionFirebaseUserInfo, bson.M{
|
||||||
|
"firebaseuserid": uid,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err, "", ""
|
||||||
|
}
|
||||||
|
if found == nil {
|
||||||
|
return errors.New("firebase info not found: " + uid), "", ""
|
||||||
|
}
|
||||||
|
provider = found["firebaseprovider"].(string)
|
||||||
|
providerid = found["firebaseproviderId"].(string)
|
||||||
|
if provider == "" || providerid == "" {
|
||||||
|
return errors.New("getProviderInfo - firebase info not found: " + provider + " / " + providerid), "", ""
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
provider = platform
|
||||||
|
providerid = uid
|
||||||
|
}
|
||||||
|
|
||||||
|
if provider == "" || providerid == "" {
|
||||||
|
return errors.New("getProviderInfo - provider info not found: " + provider + " / " + providerid), "", ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, provider, providerid
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
var errKeyNotFound = errors.New("key not found")
|
||||||
|
|
||||||
|
func downloadSigningCert(keyurl string, kid string, alg string) (rsa.PublicKey, error) {
|
||||||
|
|
||||||
|
var publicKey rsa.PublicKey
|
||||||
|
|
||||||
|
resp, err := http.Get(keyurl) // GET 호출
|
||||||
|
if err != nil {
|
||||||
|
return rsa.PublicKey{}, err
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
io.Copy(io.Discard, resp.Body)
|
||||||
|
resp.Body.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
|
// 결과 출력
|
||||||
|
data, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return rsa.PublicKey{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var parseddata map[string]interface{}
|
||||||
|
err = json.Unmarshal([]byte(data), &parseddata)
|
||||||
|
if err != nil {
|
||||||
|
return rsa.PublicKey{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
mapKeys, keysok := parseddata["keys"]
|
||||||
|
if !keysok {
|
||||||
|
return rsa.PublicKey{}, errKeyNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
keylist, keyok := mapKeys.([]interface{})
|
||||||
|
if !keyok {
|
||||||
|
return rsa.PublicKey{}, errKeyNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, key := range keylist {
|
||||||
|
alg_, alg_ok := key.(map[string]interface{})["alg"]
|
||||||
|
kty_, kty_ok := key.(map[string]interface{})["kty"]
|
||||||
|
|
||||||
|
if !alg_ok && kty_ok {
|
||||||
|
alg_ = kty_
|
||||||
|
alg_ok = kty_ok
|
||||||
|
}
|
||||||
|
|
||||||
|
kid_, kid_ok := key.(map[string]interface{})["kid"]
|
||||||
|
nkey_, nkey_ok := key.(map[string]interface{})["n"]
|
||||||
|
ekey_, ekey_ok := key.(map[string]interface{})["e"]
|
||||||
|
if alg_ok && kid_ok && nkey_ok && ekey_ok {
|
||||||
|
|
||||||
|
if (alg_.(string) == alg || (alg == "RS256" && alg_ == "RSA")) && kid_.(string) == kid {
|
||||||
|
decode_nkey_, err := base64.RawURLEncoding.DecodeString(nkey_.(string))
|
||||||
|
if err != nil {
|
||||||
|
return rsa.PublicKey{}, errors.New("n key decode fail")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return rsa.PublicKey{}, errors.New("e key decode fail")
|
||||||
|
}
|
||||||
|
if ekey_.(string) != "AQAB" && ekey_.(string) != "AAEAAQ" {
|
||||||
|
return rsa.PublicKey{}, errors.New("e key is not AQAB or AAEAAQ")
|
||||||
|
}
|
||||||
|
|
||||||
|
publicKey = rsa.PublicKey{
|
||||||
|
N: new(big.Int).SetBytes(decode_nkey_),
|
||||||
|
E: 65537,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return publicKey, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func JWTparseCode(keyurl string, code string) (string, string, string) {
|
||||||
|
|
||||||
|
parts := strings.Split(string(code), ".")
|
||||||
|
if len(parts) != 3 {
|
||||||
|
logger.Error("ErrCannotDecode:", len(parts))
|
||||||
|
return "", "", ""
|
||||||
|
}
|
||||||
|
decoded, err := base64.RawURLEncoding.DecodeString(parts[0])
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("ErrInvalidTokenPart:", string(decoded))
|
||||||
|
return "", "", ""
|
||||||
|
}
|
||||||
|
|
||||||
|
var parseddata map[string]string
|
||||||
|
err = json.Unmarshal(decoded, &parseddata)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
kid, kidok := parseddata["kid"]
|
||||||
|
alg, algok := parseddata["alg"]
|
||||||
|
|
||||||
|
if !kidok {
|
||||||
|
logger.Error("ErrorHeader_kid_not_found")
|
||||||
|
return "", "", ""
|
||||||
|
}
|
||||||
|
if !algok {
|
||||||
|
logger.Error("ErrorHeader_alg_not_found")
|
||||||
|
return "", "", ""
|
||||||
|
}
|
||||||
|
|
||||||
|
publicKey, err := downloadSigningCert(keyurl, kid, alg)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("ErrorHeader_alg_not_found:", err)
|
||||||
|
return "", "", ""
|
||||||
|
}
|
||||||
|
|
||||||
|
token, err := jwt.Parse(code, func(token *jwt.Token) (interface{}, error) {
|
||||||
|
if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok {
|
||||||
|
return nil, errors.New("Unexpected signing method:" + token.Header["alg"].(string))
|
||||||
|
}
|
||||||
|
|
||||||
|
return &publicKey, nil
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
if err := err.(*jwt.ValidationError); err != nil {
|
||||||
|
if err.Errors == jwt.ValidationErrorExpired {
|
||||||
|
logger.Error("ValidationErrorExpired:", err)
|
||||||
|
return "", "", ""
|
||||||
|
}
|
||||||
|
logger.Error("JWT Validation Error:", err)
|
||||||
|
return "", "", ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
claims, ok := token.Claims.(jwt.MapClaims)
|
||||||
|
if !ok {
|
||||||
|
logger.Error("JWT token.Claims Fail: - MapClaims")
|
||||||
|
return "", "", ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// for claim := range claims {
|
||||||
|
// fmt.Println(claim, claims[claim])
|
||||||
|
// }
|
||||||
|
|
||||||
|
nonce_, nonchk := claims["nonce"]
|
||||||
|
nonce := ""
|
||||||
|
if nonchk {
|
||||||
|
nonce = nonce_.(string)
|
||||||
|
}
|
||||||
|
|
||||||
|
email_, emailchk := claims["email"]
|
||||||
|
email := ""
|
||||||
|
if emailchk {
|
||||||
|
email = email_.(string)
|
||||||
|
}
|
||||||
|
|
||||||
|
//--- nonce 체크 필요하다.
|
||||||
|
return claims["sub"].(string), email, nonce
|
||||||
|
}
|
||||||
399
core/platformapple.go
Normal file
399
core/platformapple.go
Normal file
@ -0,0 +1,399 @@
|
|||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/json"
|
||||||
|
"encoding/pem"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"repositories.action2quare.com/ayo/go-ayo/logger"
|
||||||
|
|
||||||
|
"github.com/golang-jwt/jwt"
|
||||||
|
"go.mongodb.org/mongo-driver/bson"
|
||||||
|
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||||
|
"go.mongodb.org/mongo-driver/mongo/options"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Apple_WebValidationTokenRequest struct {
|
||||||
|
ClientID string
|
||||||
|
ClientSecret string
|
||||||
|
Code string
|
||||||
|
RedirectURI string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Apple_WebRefreshTokenRequest struct {
|
||||||
|
ClientID string
|
||||||
|
ClientSecret string
|
||||||
|
RefreshToken string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Apple_ValidationResponse struct {
|
||||||
|
AccessToken string `json:"access_token"`
|
||||||
|
TokenType string `json:"token_type"`
|
||||||
|
ExpiresIn int `json:"expires_in"`
|
||||||
|
RefreshToken string `json:"refresh_token"`
|
||||||
|
IDToken string `json:"id_token"`
|
||||||
|
Error string `json:"error"`
|
||||||
|
ErrorDescription string `json:"error_description"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mg *Maingate) platform_apple_get_login_url(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
|
browserinfo, err := mg.GetUserBrowserInfo(r)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
logger.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
existid := r.URL.Query().Get("existid")
|
||||||
|
//fmt.Println("existid =>", existid)
|
||||||
|
if existid != "" {
|
||||||
|
// 기존 계정이 있는 경우에는 그 계정 부터 조회한다.
|
||||||
|
info, err := mg.getUserTokenWithCheck(AuthPlatformApple, existid, browserinfo)
|
||||||
|
if err == nil {
|
||||||
|
if info.token != "" {
|
||||||
|
params := url.Values{}
|
||||||
|
params.Add("id", existid)
|
||||||
|
params.Add("authtype", AuthPlatformApple)
|
||||||
|
http.Redirect(w, r, "actionsquare://login?"+params.Encode(), http.StatusSeeOther)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sessionkey := mg.GeneratePlatformLoginNonceKey()
|
||||||
|
nonce := mg.GeneratePlatformLoginNonceKey()
|
||||||
|
|
||||||
|
mg.mongoClient.Delete(CollectionPlatformLoginToken, bson.M{
|
||||||
|
"platform": AuthPlatformApple,
|
||||||
|
"key": sessionkey,
|
||||||
|
})
|
||||||
|
|
||||||
|
_, _, err = mg.mongoClient.Update(CollectionPlatformLoginToken, bson.M{
|
||||||
|
"_id": primitive.NewObjectID(),
|
||||||
|
}, bson.M{
|
||||||
|
"$setOnInsert": bson.M{
|
||||||
|
"platform": AuthPlatformApple,
|
||||||
|
"key": sessionkey,
|
||||||
|
"nonce": nonce,
|
||||||
|
"brinfo": browserinfo,
|
||||||
|
},
|
||||||
|
}, options.Update().SetUpsert(true))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
logger.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
params := url.Values{}
|
||||||
|
params.Add("client_id", mg.AppleCientId)
|
||||||
|
params.Add("redirect_uri", mg.RedirectBaseUrl+"/authorize/"+AuthPlatformApple)
|
||||||
|
|
||||||
|
params.Add("response_type", "code id_token")
|
||||||
|
params.Add("scope", "name email")
|
||||||
|
params.Add("nonce", nonce)
|
||||||
|
params.Add("response_mode", "form_post")
|
||||||
|
|
||||||
|
// set cookie for storing token
|
||||||
|
cookie := http.Cookie{
|
||||||
|
Name: "LoginFlowContext_SessionKey",
|
||||||
|
Value: sessionkey,
|
||||||
|
Expires: time.Now().Add(1 * time.Hour),
|
||||||
|
//SameSite: http.SameSiteStrictMode,
|
||||||
|
SameSite: http.SameSiteLaxMode,
|
||||||
|
// HttpOnly: false,
|
||||||
|
Secure: true,
|
||||||
|
Path: "/",
|
||||||
|
}
|
||||||
|
http.SetCookie(w, &cookie)
|
||||||
|
//Set-Cookie
|
||||||
|
|
||||||
|
http.Redirect(w, r, "https://appleid.apple.com/auth/authorize?"+params.Encode(), http.StatusSeeOther)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mg *Maingate) platform_apple_authorize(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
|
defer r.Body.Close()
|
||||||
|
body, _ := io.ReadAll(r.Body)
|
||||||
|
bodyString := string(body)
|
||||||
|
code := ""
|
||||||
|
for _, params := range strings.Split(bodyString, "&") {
|
||||||
|
args := strings.Split(params, "=")
|
||||||
|
if len(args) == 2 {
|
||||||
|
if args[0] == "code" {
|
||||||
|
code = args[1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// set cookie for storing token
|
||||||
|
cookie := http.Cookie{
|
||||||
|
Name: "LoginFlowContext_code",
|
||||||
|
Value: code,
|
||||||
|
Expires: time.Now().Add(1 * time.Minute),
|
||||||
|
SameSite: http.SameSiteLaxMode,
|
||||||
|
Secure: true,
|
||||||
|
Path: "/",
|
||||||
|
}
|
||||||
|
http.SetCookie(w, &cookie)
|
||||||
|
|
||||||
|
http.Redirect(w, r, mg.RedirectBaseUrl+"/authorize_result/"+AuthPlatformApple, http.StatusSeeOther) //-- 바로 받으니까 쿠키 안와서 한번 더 Redirect 시킨다.
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mg *Maingate) platform_apple_authorize_result(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
|
brinfo, err := mg.GetUserBrowserInfo(r)
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
logger.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
cookie, err := r.Cookie("LoginFlowContext_SessionKey")
|
||||||
|
if err != nil {
|
||||||
|
logger.Println("Session not found", err)
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
cookiecode, err := r.Cookie("LoginFlowContext_code")
|
||||||
|
if err != nil {
|
||||||
|
logger.Println("code not found", err)
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
code := cookiecode.Value
|
||||||
|
|
||||||
|
found, err := mg.mongoClient.FindOne(CollectionPlatformLoginToken, bson.M{
|
||||||
|
"key": cookie.Value,
|
||||||
|
"platform": AuthPlatformApple,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
logger.Println("LoginFlowContext_SessionKey find key :", err)
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if found == nil {
|
||||||
|
logger.Println("LoginFlowContext_SessionKey not found")
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if cookie.Value != found["key"] {
|
||||||
|
logger.Println("LoginFlowContext_SessionKey key not match")
|
||||||
|
logger.Println(cookie.Value)
|
||||||
|
logger.Println(found["key"])
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if brinfo != found["brinfo"] { //-- 로그인 시작점과 인증점의 브라우저 혹은 접속지 정보가 다르다?
|
||||||
|
logger.Println("LoginFlowContext_SessionKey brinfo not match ")
|
||||||
|
logger.Println(brinfo)
|
||||||
|
logger.Println(found["brinfo"])
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate the client secret used to authenticate with Apple's validation servers
|
||||||
|
secret, err := generateClientSecret(mg.ApplePrivateKey, mg.AppleTeamId, mg.AppleServiceId, mg.AppleKeyId)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("error generating secret: ", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
vReq := Apple_WebValidationTokenRequest{
|
||||||
|
ClientID: mg.AppleServiceId,
|
||||||
|
ClientSecret: secret,
|
||||||
|
Code: code,
|
||||||
|
RedirectURI: mg.RedirectBaseUrl + "/authorize/" + AuthPlatformApple, // This URL must be validated with apple in your service
|
||||||
|
}
|
||||||
|
|
||||||
|
var resp Apple_ValidationResponse
|
||||||
|
|
||||||
|
err = verifyWebToken(context.Background(), vReq, &resp)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("error verifying: ", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.Error != "" {
|
||||||
|
logger.Errorf("apple returned an error: %s - %s\n", resp.Error, resp.ErrorDescription)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// fmt.Println("==============================")
|
||||||
|
// fmt.Println("IDToken:", resp.IDToken)
|
||||||
|
// fmt.Println("AccessToken:", resp.AccessToken)
|
||||||
|
// fmt.Println("ExpiresIn:", resp.ExpiresIn)
|
||||||
|
// fmt.Println("RefreshToken:", resp.RefreshToken)
|
||||||
|
// fmt.Println("TokenType:", resp.TokenType)
|
||||||
|
// fmt.Println("==============================")
|
||||||
|
|
||||||
|
userid, email, nonce := JWTparseCode("https://appleid.apple.com/auth/keys", resp.IDToken)
|
||||||
|
|
||||||
|
if nonce == "" || nonce != found["nonce"] {
|
||||||
|
logger.Errorf("nonce not match")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if userid != "" && email != "" {
|
||||||
|
var info usertokeninfo
|
||||||
|
info.platform = AuthPlatformApple
|
||||||
|
info.userid = userid
|
||||||
|
info.token = resp.RefreshToken
|
||||||
|
info.brinfo = brinfo
|
||||||
|
|
||||||
|
mg.setUserToken(info)
|
||||||
|
params := url.Values{}
|
||||||
|
params.Add("id", userid)
|
||||||
|
params.Add("authtype", AuthPlatformApple)
|
||||||
|
http.Redirect(w, r, "actionsquare://login?"+params.Encode(), http.StatusSeeOther)
|
||||||
|
} else {
|
||||||
|
http.Redirect(w, r, "actionsquare://error", http.StatusSeeOther)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mg *Maingate) platform_apple_getuserinfo(refreshToken string) (bool, string, string) {
|
||||||
|
//=================================RefreshToken을 사용해서 정보 가져 온다. 이미 인증된 사용자의 업데이트 목적
|
||||||
|
secret, err := generateClientSecret(mg.ApplePrivateKey, mg.AppleTeamId, mg.AppleServiceId, mg.AppleKeyId)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("error generating secret: ", err)
|
||||||
|
return false, "", ""
|
||||||
|
}
|
||||||
|
|
||||||
|
vReqRefreshToken := Apple_WebRefreshTokenRequest{
|
||||||
|
ClientID: mg.AppleServiceId,
|
||||||
|
ClientSecret: secret,
|
||||||
|
RefreshToken: refreshToken,
|
||||||
|
}
|
||||||
|
|
||||||
|
var respReferesh Apple_ValidationResponse
|
||||||
|
|
||||||
|
err = verifyRefreshToken(context.Background(), vReqRefreshToken, &respReferesh)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("error verifying: " + err.Error())
|
||||||
|
return false, "", ""
|
||||||
|
}
|
||||||
|
|
||||||
|
if respReferesh.Error != "" {
|
||||||
|
logger.Error("apple returned an error: %s - %s\n", respReferesh.Error, respReferesh.ErrorDescription)
|
||||||
|
return false, "", ""
|
||||||
|
}
|
||||||
|
|
||||||
|
userid, email, _ := JWTparseCode("https://appleid.apple.com/auth/keys", respReferesh.IDToken)
|
||||||
|
|
||||||
|
// fmt.Println("==============================")
|
||||||
|
// fmt.Println("RefreshToken")
|
||||||
|
// fmt.Println("==============================")
|
||||||
|
// fmt.Println("IDToken:", respReferesh.IDToken)
|
||||||
|
// fmt.Println("AccessToken:", respReferesh.AccessToken)
|
||||||
|
// fmt.Println("ExpiresIn:", respReferesh.ExpiresIn)
|
||||||
|
// fmt.Println("RefreshToken:", respReferesh.RefreshToken)
|
||||||
|
// fmt.Println("TokenType:", respReferesh.TokenType)
|
||||||
|
// fmt.Println("==============================")
|
||||||
|
// fmt.Println("Parse:")
|
||||||
|
// fmt.Println("userid:", userid)
|
||||||
|
// fmt.Println("email:", email)
|
||||||
|
// fmt.Println("nonce:", nonce)
|
||||||
|
|
||||||
|
return true, userid, email
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func generateClientSecret(signingKey, teamID, clientID, keyID string) (string, error) {
|
||||||
|
block, _ := pem.Decode([]byte(signingKey))
|
||||||
|
if block == nil {
|
||||||
|
return "", errors.New("empty block after decoding")
|
||||||
|
}
|
||||||
|
|
||||||
|
privKey, err := x509.ParsePKCS8PrivateKey(block.Bytes)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the Claims
|
||||||
|
now := time.Now()
|
||||||
|
claims := &jwt.StandardClaims{
|
||||||
|
Issuer: teamID,
|
||||||
|
IssuedAt: now.Unix(),
|
||||||
|
ExpiresAt: now.Add(time.Hour*24*180 - time.Second).Unix(), // 180 days
|
||||||
|
Audience: "https://appleid.apple.com",
|
||||||
|
Subject: clientID,
|
||||||
|
}
|
||||||
|
|
||||||
|
token := jwt.NewWithClaims(jwt.SigningMethodES256, claims)
|
||||||
|
token.Header["alg"] = "ES256"
|
||||||
|
token.Header["kid"] = keyID
|
||||||
|
|
||||||
|
return token.SignedString(privKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
func verifyWebToken(ctx context.Context, reqBody Apple_WebValidationTokenRequest, result interface{}) error {
|
||||||
|
data := url.Values{}
|
||||||
|
data.Set("client_id", reqBody.ClientID)
|
||||||
|
data.Set("client_secret", reqBody.ClientSecret)
|
||||||
|
data.Set("code", reqBody.Code)
|
||||||
|
data.Set("redirect_uri", reqBody.RedirectURI)
|
||||||
|
data.Set("grant_type", "authorization_code")
|
||||||
|
|
||||||
|
req, err := http.NewRequestWithContext(ctx, "POST", "https://appleid.apple.com/auth/token", strings.NewReader(data.Encode()))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Header.Add("content-type", "application/x-www-form-urlencoded")
|
||||||
|
req.Header.Add("accept", "application/json")
|
||||||
|
req.Header.Add("user-agent", "go-signin-with-apple") // apple requires a user agent
|
||||||
|
|
||||||
|
client := &http.Client{}
|
||||||
|
res, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer res.Body.Close()
|
||||||
|
|
||||||
|
return json.NewDecoder(res.Body).Decode(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
func verifyRefreshToken(ctx context.Context, reqBody Apple_WebRefreshTokenRequest, result interface{}) error {
|
||||||
|
data := url.Values{}
|
||||||
|
data.Set("client_id", reqBody.ClientID)
|
||||||
|
data.Set("client_secret", reqBody.ClientSecret)
|
||||||
|
data.Set("grant_type", "refresh_token")
|
||||||
|
data.Set("refresh_token", reqBody.RefreshToken)
|
||||||
|
|
||||||
|
//return doRequest(ctx, c.client, &result, c.validationURL, data)
|
||||||
|
|
||||||
|
req, err := http.NewRequestWithContext(ctx, "POST", "https://appleid.apple.com/auth/token", strings.NewReader(data.Encode()))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Header.Add("content-type", "application/x-www-form-urlencoded")
|
||||||
|
req.Header.Add("accept", "application/json")
|
||||||
|
req.Header.Add("user-agent", "go-signin-with-apple") // apple requires a user agent
|
||||||
|
|
||||||
|
client := &http.Client{}
|
||||||
|
res, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer res.Body.Close()
|
||||||
|
|
||||||
|
return json.NewDecoder(res.Body).Decode(result)
|
||||||
|
}
|
||||||
269
core/platformfirebaseauth.go
Normal file
269
core/platformfirebaseauth.go
Normal file
@ -0,0 +1,269 @@
|
|||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"repositories.action2quare.com/ayo/go-ayo/logger"
|
||||||
|
|
||||||
|
"go.mongodb.org/mongo-driver/bson"
|
||||||
|
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||||
|
"go.mongodb.org/mongo-driver/mongo/options"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (mg *Maingate) platform_firebaseauth_get_login_url(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
|
browserinfo, err := mg.GetUserBrowserInfo(r)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
logger.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
existid := r.URL.Query().Get("existid")
|
||||||
|
withSDK := r.URL.Query().Get("withSDK")
|
||||||
|
|
||||||
|
// 무조껀 SDK 있어야됨
|
||||||
|
if withSDK != "1" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//fmt.Println("existid =>", existid)
|
||||||
|
if existid != "" {
|
||||||
|
//기존 계정이 있는 경우에는 그 계정 부터 조회한다.
|
||||||
|
info, err := mg.getUserTokenWithCheck(AuthPlatformFirebaseAuth, existid, browserinfo)
|
||||||
|
if err == nil {
|
||||||
|
if info.token != "" {
|
||||||
|
params := url.Values{}
|
||||||
|
params.Add("id", existid)
|
||||||
|
params.Add("authtype", AuthPlatformFirebaseAuth)
|
||||||
|
if withSDK == "1" {
|
||||||
|
w.Write([]byte("?" + params.Encode()))
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sessionkey := mg.GeneratePlatformLoginNonceKey()
|
||||||
|
nonce := mg.GeneratePlatformLoginNonceKey()
|
||||||
|
|
||||||
|
mg.mongoClient.Delete(CollectionPlatformLoginToken, bson.M{
|
||||||
|
"platform": AuthPlatformFirebaseAuth,
|
||||||
|
"key": sessionkey,
|
||||||
|
})
|
||||||
|
|
||||||
|
_, _, err = mg.mongoClient.Update(CollectionPlatformLoginToken, bson.M{
|
||||||
|
"_id": primitive.NewObjectID(),
|
||||||
|
}, bson.M{
|
||||||
|
"$setOnInsert": bson.M{
|
||||||
|
"platform": AuthPlatformFirebaseAuth,
|
||||||
|
"key": sessionkey,
|
||||||
|
"nonce": nonce,
|
||||||
|
"brinfo": browserinfo,
|
||||||
|
},
|
||||||
|
}, options.Update().SetUpsert(true))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
logger.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// set cookie for storing token
|
||||||
|
cookie := http.Cookie{
|
||||||
|
Name: "LoginFlowContext_SessionKey",
|
||||||
|
Value: sessionkey,
|
||||||
|
Expires: time.Now().Add(1 * time.Hour),
|
||||||
|
//SameSite: http.SameSiteStrictMode,
|
||||||
|
SameSite: http.SameSiteLaxMode,
|
||||||
|
// HttpOnly: false,
|
||||||
|
Secure: true,
|
||||||
|
Path: "/",
|
||||||
|
}
|
||||||
|
http.SetCookie(w, &cookie)
|
||||||
|
|
||||||
|
if withSDK == "1" {
|
||||||
|
params := url.Values{}
|
||||||
|
params.Add("nonce", nonce)
|
||||||
|
w.Write([]byte("?" + params.Encode()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type FirebaseSDKAuthInfo struct {
|
||||||
|
MemberId string `json:"id"`
|
||||||
|
Code string `json:"code"`
|
||||||
|
State string `json:"state"`
|
||||||
|
Nickname string `json:"nickname"`
|
||||||
|
Provider string `json:"provider"`
|
||||||
|
ProviderId string `json:"providerId"`
|
||||||
|
Email string `json:"email"`
|
||||||
|
PhotoUrl string `json:"photourl"`
|
||||||
|
PhoneNumber string `json:"phonenumber"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mg *Maingate) platform_firebaseauth_authorize_sdk(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
|
defer r.Body.Close()
|
||||||
|
|
||||||
|
brinfo, err := mg.GetUserBrowserInfo(r)
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
logger.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
cookie, err := r.Cookie("LoginFlowContext_SessionKey")
|
||||||
|
if err != nil {
|
||||||
|
logger.Println("Session not found", err)
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var authinfo FirebaseSDKAuthInfo
|
||||||
|
|
||||||
|
err = json.NewDecoder(r.Body).Decode(&authinfo)
|
||||||
|
if err != nil {
|
||||||
|
logger.Println("authinfo decoding fail:", err)
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
bSuccess, Result := mg.platform_firebaseauth_authorize_raw(w, brinfo, authinfo.Code, authinfo.State,
|
||||||
|
cookie.Value, authinfo.MemberId, authinfo.Nickname, authinfo.Provider, authinfo.ProviderId, authinfo.Email, authinfo.PhotoUrl, authinfo.PhoneNumber)
|
||||||
|
|
||||||
|
if bSuccess {
|
||||||
|
w.Write([]byte("?" + Result))
|
||||||
|
//http.Redirect(w, r, "actionsquare://login?"+Result, http.StatusSeeOther)
|
||||||
|
} else {
|
||||||
|
http.Redirect(w, r, "actionsquare://error", http.StatusSeeOther)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mg *Maingate) platform_firebaseauth_authorize_raw(w http.ResponseWriter, brinfo, code, state, cookieSessionKey, memberId, nickname, provider, providerId, email, photourl, phonenumber string) (bool, string) {
|
||||||
|
|
||||||
|
found, err := mg.mongoClient.FindOne(CollectionPlatformLoginToken, bson.M{
|
||||||
|
"platform": AuthPlatformFirebaseAuth,
|
||||||
|
"key": cookieSessionKey,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
logger.Println("LoginFlowContext_SessionKey find key :", err)
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
return false, ""
|
||||||
|
}
|
||||||
|
|
||||||
|
if found == nil {
|
||||||
|
logger.Println("LoginFlowContext_SessionKey not found")
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
return false, ""
|
||||||
|
}
|
||||||
|
|
||||||
|
if cookieSessionKey != found["key"] {
|
||||||
|
logger.Println("LoginFlowContext_SessionKey key not match")
|
||||||
|
logger.Println(cookieSessionKey)
|
||||||
|
logger.Println(found["key"])
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
return false, ""
|
||||||
|
}
|
||||||
|
|
||||||
|
if state != found["nonce"] {
|
||||||
|
logger.Println("LoginFlowContext_SessionKey nonce not match")
|
||||||
|
logger.Println(state)
|
||||||
|
logger.Println(found["nonce"])
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
return false, ""
|
||||||
|
}
|
||||||
|
|
||||||
|
if brinfo != found["brinfo"] { //-- 로그인 시작점과 인증점의 브라우저 혹은 접속지 정보가 다르다?
|
||||||
|
logger.Println("LoginFlowContext_SessionKey brinfo not match ")
|
||||||
|
logger.Println(brinfo)
|
||||||
|
logger.Println(found["brinfo"])
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
return false, ""
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = mg.firebaseAppClient.VerifyIDToken(mg.firebaseAppContext, code)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("error verifying ID token:", err)
|
||||||
|
return false, ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// log.Println("Verified ID token: ", token)
|
||||||
|
// log.Println("Verified ID token: ", token.UID)
|
||||||
|
|
||||||
|
acceestoken_expire_time := time.Date(2999, 1, int(time.January), 0, 0, 0, 0, time.UTC).Unix()
|
||||||
|
|
||||||
|
if memberId != "" && provider != "" && providerId != "" {
|
||||||
|
var info usertokeninfo
|
||||||
|
info.platform = AuthPlatformFirebaseAuth
|
||||||
|
info.userid = memberId
|
||||||
|
|
||||||
|
info.token = code
|
||||||
|
info.brinfo = brinfo
|
||||||
|
info.accesstoken = ""
|
||||||
|
info.accesstoken_expire_time = acceestoken_expire_time
|
||||||
|
mg.setUserToken(info)
|
||||||
|
|
||||||
|
mg.mongoClient.Delete(CollectionFirebaseUserInfo, bson.M{
|
||||||
|
"firebaseuserid": info.userid,
|
||||||
|
})
|
||||||
|
|
||||||
|
_, _, err := mg.mongoClient.Update(CollectionFirebaseUserInfo, bson.M{
|
||||||
|
"_id": primitive.NewObjectID(),
|
||||||
|
}, bson.M{
|
||||||
|
"$setOnInsert": bson.M{
|
||||||
|
"firebaseuserid": memberId,
|
||||||
|
"firebasenickname": nickname,
|
||||||
|
"firebaseprovider": provider,
|
||||||
|
"firebaseproviderId": providerId,
|
||||||
|
"firebaseemail": email,
|
||||||
|
"firebasephotourl": photourl,
|
||||||
|
"firebasephonenumber": phonenumber,
|
||||||
|
"updatetime": time.Now(),
|
||||||
|
},
|
||||||
|
}, options.Update().SetUpsert(true))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
logger.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
params := url.Values{}
|
||||||
|
params.Add("id", memberId)
|
||||||
|
params.Add("authtype", AuthPlatformFirebaseAuth)
|
||||||
|
return true, params.Encode()
|
||||||
|
}
|
||||||
|
return false, ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mg *Maingate) platform_firebase_getuserinfo(info usertokeninfo) (bool, string, string) {
|
||||||
|
|
||||||
|
found, err := mg.mongoClient.FindOne(CollectionFirebaseUserInfo, bson.M{
|
||||||
|
"firebaseuserid": info.userid,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
logger.Error(err)
|
||||||
|
return false, "", ""
|
||||||
|
}
|
||||||
|
if found == nil {
|
||||||
|
logger.Error(errors.New("firebase info not found: " + info.userid))
|
||||||
|
return false, "", ""
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = mg.firebaseAppClient.VerifyIDToken(mg.firebaseAppContext, info.token)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("error verifying ID token:", err)
|
||||||
|
return false, "", ""
|
||||||
|
}
|
||||||
|
|
||||||
|
tempEmail := found["firebaseemail"].(string)
|
||||||
|
|
||||||
|
return true, info.userid, tempEmail
|
||||||
|
|
||||||
|
}
|
||||||
344
core/platformgamepot.go
Normal file
344
core/platformgamepot.go
Normal file
@ -0,0 +1,344 @@
|
|||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"repositories.action2quare.com/ayo/go-ayo/logger"
|
||||||
|
|
||||||
|
"go.mongodb.org/mongo-driver/bson"
|
||||||
|
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||||
|
"go.mongodb.org/mongo-driver/mongo/options"
|
||||||
|
)
|
||||||
|
|
||||||
|
type GamepotTemplate struct {
|
||||||
|
RedirectBaseUrl string
|
||||||
|
State string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Gamepot_LoginValidationResponse struct {
|
||||||
|
Message string `json:"message"`
|
||||||
|
Status int `json:"status"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mg *Maingate) platform_gamepot_get_login_url(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
|
browserinfo, err := mg.GetUserBrowserInfo(r)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
logger.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
existid := r.URL.Query().Get("existid")
|
||||||
|
withSDK := r.URL.Query().Get("withSDK")
|
||||||
|
|
||||||
|
//fmt.Println("existid =>", existid)
|
||||||
|
if existid != "" {
|
||||||
|
//기존 계정이 있는 경우에는 그 계정 부터 조회한다.
|
||||||
|
info, err := mg.getUserTokenWithCheck(AuthPlatformGamepot, existid, browserinfo)
|
||||||
|
if err == nil {
|
||||||
|
if info.token != "" {
|
||||||
|
params := url.Values{}
|
||||||
|
params.Add("id", existid)
|
||||||
|
params.Add("authtype", AuthPlatformGamepot)
|
||||||
|
if withSDK == "1" {
|
||||||
|
w.Write([]byte("?" + params.Encode()))
|
||||||
|
} else {
|
||||||
|
http.Redirect(w, r, "actionsquare://login?"+params.Encode(), http.StatusSeeOther)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sessionkey := mg.GeneratePlatformLoginNonceKey()
|
||||||
|
nonce := mg.GeneratePlatformLoginNonceKey()
|
||||||
|
|
||||||
|
mg.mongoClient.Delete(CollectionPlatformLoginToken, bson.M{
|
||||||
|
"platform": AuthPlatformGamepot,
|
||||||
|
"key": sessionkey,
|
||||||
|
})
|
||||||
|
|
||||||
|
_, _, err = mg.mongoClient.Update(CollectionPlatformLoginToken, bson.M{
|
||||||
|
"_id": primitive.NewObjectID(),
|
||||||
|
}, bson.M{
|
||||||
|
"$setOnInsert": bson.M{
|
||||||
|
"platform": AuthPlatformGamepot,
|
||||||
|
"key": sessionkey,
|
||||||
|
"nonce": nonce,
|
||||||
|
"brinfo": browserinfo,
|
||||||
|
},
|
||||||
|
}, options.Update().SetUpsert(true))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
logger.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// set cookie for storing token
|
||||||
|
cookie := http.Cookie{
|
||||||
|
Name: "LoginFlowContext_SessionKey",
|
||||||
|
Value: sessionkey,
|
||||||
|
Expires: time.Now().Add(1 * time.Hour),
|
||||||
|
//SameSite: http.SameSiteStrictMode,
|
||||||
|
SameSite: http.SameSiteLaxMode,
|
||||||
|
// HttpOnly: false,
|
||||||
|
Secure: true,
|
||||||
|
Path: "/",
|
||||||
|
}
|
||||||
|
http.SetCookie(w, &cookie)
|
||||||
|
|
||||||
|
if withSDK == "1" {
|
||||||
|
params := url.Values{}
|
||||||
|
params.Add("nonce", nonce)
|
||||||
|
w.Write([]byte("?" + params.Encode()))
|
||||||
|
} else {
|
||||||
|
var templateVar GamepotTemplate
|
||||||
|
templateVar.RedirectBaseUrl = mg.RedirectBaseUrl
|
||||||
|
templateVar.State = nonce
|
||||||
|
mg.webTemplate[AuthPlatformGamepot].Execute(w, templateVar)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mg *Maingate) platform_gamepot_authorize(w http.ResponseWriter, r *http.Request) {
|
||||||
|
defer r.Body.Close()
|
||||||
|
|
||||||
|
brinfo, err := mg.GetUserBrowserInfo(r)
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
logger.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
cookie, err := r.Cookie("LoginFlowContext_SessionKey")
|
||||||
|
if err != nil {
|
||||||
|
logger.Println("Session not found", err)
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
r.ParseForm()
|
||||||
|
|
||||||
|
code := r.Form.Get("code")
|
||||||
|
state := r.Form.Get("state")
|
||||||
|
|
||||||
|
gamepotmemberId := r.Form.Get("id")
|
||||||
|
gamepotnickname := r.Form.Get("nickname")
|
||||||
|
gamepotprovider := r.Form.Get("provider")
|
||||||
|
gamepotproviderId := r.Form.Get("providerId")
|
||||||
|
// gamepotverify := r.Form.Get("verify")
|
||||||
|
// gamepotagree := r.Form.Get("agree")
|
||||||
|
|
||||||
|
bSuccess, Result := mg.platform_gamepot_authorize_raw(w, brinfo, code, state, cookie.Value, gamepotmemberId, gamepotnickname, gamepotprovider, gamepotproviderId)
|
||||||
|
|
||||||
|
if bSuccess {
|
||||||
|
http.Redirect(w, r, "actionsquare://login?"+Result, http.StatusSeeOther)
|
||||||
|
} else {
|
||||||
|
http.Redirect(w, r, "actionsquare://error", http.StatusSeeOther)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type GamePotSDKAuthInfo struct {
|
||||||
|
Code string `json:"code"`
|
||||||
|
State string `json:"state"`
|
||||||
|
MemberId string `json:"id"`
|
||||||
|
Nickname string `json:"nickname"`
|
||||||
|
Provider string `json:"provider"`
|
||||||
|
ProviderId string `json:"providerId"`
|
||||||
|
// Verify string `json:"verify"`
|
||||||
|
// Agree string `json:"agree"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mg *Maingate) platform_gamepot_authorize_sdk(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
|
defer r.Body.Close()
|
||||||
|
|
||||||
|
brinfo, err := mg.GetUserBrowserInfo(r)
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
logger.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
cookie, err := r.Cookie("LoginFlowContext_SessionKey")
|
||||||
|
if err != nil {
|
||||||
|
logger.Println("Session not found", err)
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var authinfo GamePotSDKAuthInfo
|
||||||
|
|
||||||
|
err = json.NewDecoder(r.Body).Decode(&authinfo)
|
||||||
|
if err != nil {
|
||||||
|
logger.Println("authinfo decoding fail:", err)
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
bSuccess, Result := mg.platform_gamepot_authorize_raw(w, brinfo, authinfo.Code, authinfo.State,
|
||||||
|
cookie.Value, authinfo.MemberId, authinfo.Nickname, authinfo.Provider, authinfo.ProviderId)
|
||||||
|
|
||||||
|
if bSuccess {
|
||||||
|
w.Write([]byte("?" + Result))
|
||||||
|
//http.Redirect(w, r, "actionsquare://login?"+Result, http.StatusSeeOther)
|
||||||
|
} else {
|
||||||
|
http.Redirect(w, r, "actionsquare://error", http.StatusSeeOther)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mg *Maingate) platform_gamepot_authorize_raw(w http.ResponseWriter, brinfo, code, state, cookieSessionKey, gamepotmemberId, gamepotnickname, gamepotprovider, gamepotproviderId string) (bool, string) {
|
||||||
|
|
||||||
|
found, err := mg.mongoClient.FindOne(CollectionPlatformLoginToken, bson.M{
|
||||||
|
"platform": AuthPlatformGamepot,
|
||||||
|
"key": cookieSessionKey,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
logger.Println("LoginFlowContext_SessionKey find key :", err)
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
return false, ""
|
||||||
|
}
|
||||||
|
|
||||||
|
if found == nil {
|
||||||
|
logger.Println("LoginFlowContext_SessionKey not found")
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
return false, ""
|
||||||
|
}
|
||||||
|
|
||||||
|
if cookieSessionKey != found["key"] {
|
||||||
|
logger.Println("LoginFlowContext_SessionKey key not match")
|
||||||
|
logger.Println(cookieSessionKey)
|
||||||
|
logger.Println(found["key"])
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
return false, ""
|
||||||
|
}
|
||||||
|
|
||||||
|
if state != found["nonce"] {
|
||||||
|
logger.Println("LoginFlowContext_SessionKey nonce not match")
|
||||||
|
logger.Println(state)
|
||||||
|
logger.Println(found["nonce"])
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
return false, ""
|
||||||
|
}
|
||||||
|
|
||||||
|
if brinfo != found["brinfo"] { //-- 로그인 시작점과 인증점의 브라우저 혹은 접속지 정보가 다르다?
|
||||||
|
logger.Println("LoginFlowContext_SessionKey brinfo not match ")
|
||||||
|
logger.Println(brinfo)
|
||||||
|
logger.Println(found["brinfo"])
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
return false, ""
|
||||||
|
}
|
||||||
|
|
||||||
|
//=================
|
||||||
|
params := url.Values{}
|
||||||
|
params.Add("projectId", mg.GamepotProjectId)
|
||||||
|
params.Add("memberId", gamepotmemberId)
|
||||||
|
params.Add("token", code)
|
||||||
|
|
||||||
|
var respLoginCheck Gamepot_LoginValidationResponse
|
||||||
|
content := params.Encode()
|
||||||
|
resp, _ := http.Post(mg.GamepotLoginCheckAPIURL, "application/json", bytes.NewBuffer([]byte(content)))
|
||||||
|
if resp != nil {
|
||||||
|
body, _ := io.ReadAll(resp.Body)
|
||||||
|
json.Unmarshal(body, &respLoginCheck)
|
||||||
|
} else {
|
||||||
|
logger.Println("gamepot logincheck fail.")
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
return false, ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// fmt.Println("==============================")
|
||||||
|
// fmt.Println("respLoginCheck.Status:", respLoginCheck.Status)
|
||||||
|
// fmt.Println("respLoginCheck.Message:", respLoginCheck.Message)
|
||||||
|
// fmt.Println("==============================")
|
||||||
|
|
||||||
|
if respLoginCheck.Status != 0 {
|
||||||
|
logger.Errorf("gamepot login fail:", respLoginCheck.Message)
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
return false, ""
|
||||||
|
}
|
||||||
|
|
||||||
|
acceestoken_expire_time := time.Date(2999, 1, int(time.January), 0, 0, 0, 0, time.UTC).Unix()
|
||||||
|
|
||||||
|
if gamepotmemberId != "" && gamepotprovider != "" && gamepotproviderId != "" {
|
||||||
|
var info usertokeninfo
|
||||||
|
info.platform = AuthPlatformGamepot
|
||||||
|
info.userid = gamepotmemberId
|
||||||
|
//== memberid 제외하고는 모두 client로 부터 온 값이기 때문에 유효성이 확인된 값이 아니다. 하지만, 참조용으로 사용은 한다.
|
||||||
|
// 정확한 정보는 gamepotid를 gamepot dashboard에서 조회해서 확인 할 수 밖에 없다.
|
||||||
|
info.token = gamepotprovider + "-" + gamepotproviderId
|
||||||
|
info.brinfo = brinfo
|
||||||
|
info.accesstoken = ""
|
||||||
|
info.accesstoken_expire_time = acceestoken_expire_time
|
||||||
|
mg.setUserToken(info)
|
||||||
|
|
||||||
|
mg.mongoClient.Delete(CollectionGamepotUserInfo, bson.M{
|
||||||
|
"gamepotuserid": info.userid,
|
||||||
|
})
|
||||||
|
|
||||||
|
_, _, err := mg.mongoClient.Update(CollectionGamepotUserInfo, bson.M{
|
||||||
|
"_id": primitive.NewObjectID(),
|
||||||
|
}, bson.M{
|
||||||
|
"$setOnInsert": bson.M{
|
||||||
|
"gamepotuserid": gamepotmemberId,
|
||||||
|
"gamepotnickname": gamepotnickname,
|
||||||
|
"gamepotprovider": gamepotprovider,
|
||||||
|
"gamepotproviderId": gamepotproviderId,
|
||||||
|
// "gamepotverify": gamepotverify,
|
||||||
|
// "gamepotagree": gamepotagree,
|
||||||
|
"updatetime": time.Now(),
|
||||||
|
},
|
||||||
|
}, options.Update().SetUpsert(true))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
logger.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
params := url.Values{}
|
||||||
|
params.Add("id", gamepotmemberId)
|
||||||
|
params.Add("authtype", AuthPlatformGamepot)
|
||||||
|
return true, params.Encode()
|
||||||
|
}
|
||||||
|
return false, ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mg *Maingate) platform_gamepot_getuserinfo(info usertokeninfo) (bool, string, string) {
|
||||||
|
|
||||||
|
found, err := mg.mongoClient.FindOne(CollectionGamepotUserInfo, bson.M{
|
||||||
|
"gamepotuserid": info.userid,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
logger.Error(err)
|
||||||
|
return false, "", ""
|
||||||
|
}
|
||||||
|
if found == nil {
|
||||||
|
logger.Error(errors.New("gamepot info not found: " + info.userid))
|
||||||
|
return false, "", ""
|
||||||
|
}
|
||||||
|
|
||||||
|
gamepotprovider := found["gamepotprovider"].(string)
|
||||||
|
gamepotproviderId := found["gamepotproviderId"].(string)
|
||||||
|
|
||||||
|
if gamepotprovider+"-"+gamepotproviderId != info.token {
|
||||||
|
logger.Println("gamepot info not match..") //-- token은 플랫폼종류+플랫폼ID로 구성했는데... 검증할 방법이 없어서 client로 부터 온값을 쓴다. 그래도 유저가 조작하지 않는 이상 일치해야 된다.
|
||||||
|
logger.Println(info.token)
|
||||||
|
logger.Println(gamepotprovider + "-" + gamepotproviderId)
|
||||||
|
return false, "", ""
|
||||||
|
}
|
||||||
|
|
||||||
|
tempEmail := info.userid + "@gamepot" //-- 게임팟은 email을 안줘서 일단 gamepotid기준으로 임시값을 할당한다.
|
||||||
|
|
||||||
|
return true, info.userid, tempEmail
|
||||||
|
|
||||||
|
}
|
||||||
361
core/platformgoogle.go
Normal file
361
core/platformgoogle.go
Normal file
@ -0,0 +1,361 @@
|
|||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"repositories.action2quare.com/ayo/go-ayo/logger"
|
||||||
|
|
||||||
|
"go.mongodb.org/mongo-driver/bson"
|
||||||
|
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||||
|
"go.mongodb.org/mongo-driver/mongo/options"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Google_ValidationResponse struct {
|
||||||
|
IDToken string `json:"id_token"`
|
||||||
|
RefreshToken string `json:"refresh_token"`
|
||||||
|
AccessToken string `json:"access_token"`
|
||||||
|
TokenType string `json:"token_type"`
|
||||||
|
Scope string `json:"scope"`
|
||||||
|
ExpiresIn int `json:"expires_in"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Google_UserInfoResponse struct {
|
||||||
|
Sub string `json:"sub"`
|
||||||
|
Givenname string `json:"given_name"`
|
||||||
|
Familyname string `json:"family_name"`
|
||||||
|
Email string `json:"email"`
|
||||||
|
Locale string `json:"locale"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mg *Maingate) platform_google_get_login_url(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
|
browserinfo, err := mg.GetUserBrowserInfo(r)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
logger.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
existid := r.URL.Query().Get("existid")
|
||||||
|
//fmt.Println("existid =>", existid)
|
||||||
|
if existid != "" {
|
||||||
|
//기존 계정이 있는 경우에는 그 계정 부터 조회한다.
|
||||||
|
info, err := mg.getUserTokenWithCheck(AuthPlatformGoogle, existid, browserinfo)
|
||||||
|
if err == nil {
|
||||||
|
if info.token != "" {
|
||||||
|
params := url.Values{}
|
||||||
|
params.Add("id", existid)
|
||||||
|
params.Add("authtype", AuthPlatformGoogle)
|
||||||
|
http.Redirect(w, r, "actionsquare://login?"+params.Encode(), http.StatusSeeOther)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sessionkey := mg.GeneratePlatformLoginNonceKey()
|
||||||
|
nonce := mg.GeneratePlatformLoginNonceKey()
|
||||||
|
|
||||||
|
mg.mongoClient.Delete(CollectionPlatformLoginToken, bson.M{
|
||||||
|
"platform": AuthPlatformGoogle,
|
||||||
|
"key": sessionkey,
|
||||||
|
})
|
||||||
|
|
||||||
|
_, _, err = mg.mongoClient.Update(CollectionPlatformLoginToken, bson.M{
|
||||||
|
"_id": primitive.NewObjectID(),
|
||||||
|
}, bson.M{
|
||||||
|
"$setOnInsert": bson.M{
|
||||||
|
"platform": AuthPlatformGoogle,
|
||||||
|
"key": sessionkey,
|
||||||
|
"nonce": nonce,
|
||||||
|
"brinfo": browserinfo,
|
||||||
|
},
|
||||||
|
}, options.Update().SetUpsert(true))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
logger.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
params := url.Values{}
|
||||||
|
params.Add("client_id", mg.GoogleClientId)
|
||||||
|
params.Add("response_type", "code")
|
||||||
|
params.Add("redirect_uri", mg.RedirectBaseUrl+"/authorize/"+AuthPlatformGoogle)
|
||||||
|
params.Add("scope", "https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/userinfo.email")
|
||||||
|
params.Add("access_type", "offline")
|
||||||
|
params.Add("prompt", "consent")
|
||||||
|
params.Add("state", nonce)
|
||||||
|
|
||||||
|
// set cookie for storing token
|
||||||
|
cookie := http.Cookie{
|
||||||
|
Name: "LoginFlowContext_SessionKey",
|
||||||
|
Value: sessionkey,
|
||||||
|
Expires: time.Now().Add(1 * time.Hour),
|
||||||
|
//SameSite: http.SameSiteStrictMode,
|
||||||
|
SameSite: http.SameSiteLaxMode,
|
||||||
|
// HttpOnly: false,
|
||||||
|
Secure: true,
|
||||||
|
Path: "/",
|
||||||
|
}
|
||||||
|
http.SetCookie(w, &cookie)
|
||||||
|
|
||||||
|
//Set-Cookie
|
||||||
|
//fmt.Println(mg.authorizationEndpoints[AuthPlatformGoogle] + params.Encode())
|
||||||
|
|
||||||
|
//http.Redirect(w, r, "https://appleid.apple.com/auth/authorize?"+params.Encode(), http.StatusSeeOther)
|
||||||
|
http.Redirect(w, r, mg.authorizationEndpoints[AuthPlatformGoogle]+"?"+params.Encode(), http.StatusSeeOther)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mg *Maingate) platform_google_authorize(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
|
defer r.Body.Close()
|
||||||
|
code := r.URL.Query().Get("code")
|
||||||
|
state := r.URL.Query().Get("state")
|
||||||
|
|
||||||
|
// set cookie for storing token
|
||||||
|
cookie := http.Cookie{
|
||||||
|
Name: "LoginFlowContext_code",
|
||||||
|
Value: code,
|
||||||
|
Expires: time.Now().Add(1 * time.Minute),
|
||||||
|
SameSite: http.SameSiteLaxMode,
|
||||||
|
Secure: true,
|
||||||
|
Path: "/",
|
||||||
|
}
|
||||||
|
http.SetCookie(w, &cookie)
|
||||||
|
|
||||||
|
// set cookie for storing token
|
||||||
|
cookie2 := http.Cookie{
|
||||||
|
Name: "LoginFlowContext_code_state",
|
||||||
|
Value: state,
|
||||||
|
Expires: time.Now().Add(1 * time.Minute),
|
||||||
|
SameSite: http.SameSiteLaxMode,
|
||||||
|
Secure: true,
|
||||||
|
Path: "/",
|
||||||
|
}
|
||||||
|
http.SetCookie(w, &cookie2)
|
||||||
|
|
||||||
|
http.Redirect(w, r, mg.RedirectBaseUrl+"/authorize_result/"+AuthPlatformGoogle, http.StatusSeeOther) //-- 바로 받으니까 쿠키 안와서 한번 더 Redirect 시킨다.
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mg *Maingate) platform_google_authorize_result(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
|
brinfo, err := mg.GetUserBrowserInfo(r)
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
logger.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
cookie, err := r.Cookie("LoginFlowContext_SessionKey")
|
||||||
|
if err != nil {
|
||||||
|
logger.Println("Session not found", err)
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
cookiecode, err := r.Cookie("LoginFlowContext_code")
|
||||||
|
if err != nil {
|
||||||
|
logger.Println("code not found", err)
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
cookiestate, err := r.Cookie("LoginFlowContext_code_state")
|
||||||
|
if err != nil {
|
||||||
|
logger.Println("state not found", err)
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
code := cookiecode.Value
|
||||||
|
state := cookiestate.Value
|
||||||
|
|
||||||
|
found, err := mg.mongoClient.FindOne(CollectionPlatformLoginToken, bson.M{
|
||||||
|
"key": cookie.Value,
|
||||||
|
"platform": AuthPlatformGoogle,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
logger.Println("LoginFlowContext_SessionKey find key :", err)
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if found == nil {
|
||||||
|
logger.Println("LoginFlowContext_SessionKey not found")
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if cookie.Value != found["key"] {
|
||||||
|
logger.Println("LoginFlowContext_SessionKey key not match")
|
||||||
|
logger.Println(cookie.Value)
|
||||||
|
logger.Println(found["key"])
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if brinfo != found["brinfo"] { //-- 로그인 시작점과 인증점의 브라우저 혹은 접속지 정보가 다르다?
|
||||||
|
logger.Println("LoginFlowContext_SessionKey brinfo not match ")
|
||||||
|
logger.Println(brinfo)
|
||||||
|
logger.Println(found["brinfo"])
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//=================
|
||||||
|
params := url.Values{}
|
||||||
|
params.Add("client_id", mg.GoogleClientId)
|
||||||
|
params.Add("redirect_uri", mg.RedirectBaseUrl+"/authorize/"+AuthPlatformGoogle)
|
||||||
|
params.Add("client_secret", mg.GoogleClientSecret)
|
||||||
|
params.Add("code", code)
|
||||||
|
params.Add("grant_type", "authorization_code")
|
||||||
|
|
||||||
|
var respReferesh Google_ValidationResponse
|
||||||
|
acceestoken_expire_time := time.Now().Unix()
|
||||||
|
content := params.Encode()
|
||||||
|
resp, _ := http.Post(mg.tokenEndpoints[AuthPlatformGoogle], "application/x-www-form-urlencoded", bytes.NewBuffer([]byte(content)))
|
||||||
|
if resp != nil {
|
||||||
|
body, _ := io.ReadAll(resp.Body)
|
||||||
|
json.Unmarshal(body, &respReferesh)
|
||||||
|
} else {
|
||||||
|
logger.Println("tokenEndpoints fail.")
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// fmt.Println("==============================")
|
||||||
|
// fmt.Println("IDToken:", respReferesh.IDToken)
|
||||||
|
// fmt.Println("AccessToken:", respReferesh.AccessToken)
|
||||||
|
// fmt.Println("ExpiresIn:", respReferesh.ExpiresIn)
|
||||||
|
// fmt.Println("RefreshToken:", respReferesh.RefreshToken)
|
||||||
|
// fmt.Println("TokenType:", respReferesh.TokenType)
|
||||||
|
// fmt.Println("Scope:", respReferesh.Scope)
|
||||||
|
// fmt.Println("==============================")
|
||||||
|
|
||||||
|
if respReferesh.RefreshToken == "" {
|
||||||
|
logger.Errorf("RefreshToken not found")
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
acceestoken_expire_time = acceestoken_expire_time + (int64(respReferesh.ExpiresIn) * 3 / 4) //--- 3/4 이상 지나면 업데이트 한다. ms는 보통 3600초 1시간
|
||||||
|
|
||||||
|
userid, email, _ := JWTparseCode(mg.jwksUri[AuthPlatformGoogle], respReferesh.IDToken)
|
||||||
|
|
||||||
|
if state == "" || state != found["nonce"] {
|
||||||
|
logger.Errorf("nonce not match")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if userid != "" && email != "" {
|
||||||
|
var info usertokeninfo
|
||||||
|
info.platform = AuthPlatformGoogle
|
||||||
|
info.userid = userid
|
||||||
|
info.token = respReferesh.RefreshToken
|
||||||
|
info.brinfo = brinfo
|
||||||
|
info.accesstoken = respReferesh.AccessToken
|
||||||
|
info.accesstoken_expire_time = acceestoken_expire_time
|
||||||
|
mg.setUserToken(info)
|
||||||
|
|
||||||
|
params := url.Values{}
|
||||||
|
params.Add("id", userid)
|
||||||
|
params.Add("authtype", AuthPlatformGoogle)
|
||||||
|
http.Redirect(w, r, "actionsquare://login?"+params.Encode(), http.StatusSeeOther)
|
||||||
|
} else {
|
||||||
|
http.Redirect(w, r, "actionsquare://error", http.StatusSeeOther)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mg *Maingate) platform_google_getuserinfo(info usertokeninfo) (bool, string, string) {
|
||||||
|
|
||||||
|
// fmt.Println(info.platform)
|
||||||
|
// fmt.Println(info.userid)
|
||||||
|
// fmt.Println(info.accesstoken)
|
||||||
|
// fmt.Println(time.Now().Unix())
|
||||||
|
// fmt.Println(info.accesstoken_expire_time)
|
||||||
|
|
||||||
|
//-- access token 갱신이 필요하다. -- userinfoEndpoint
|
||||||
|
if time.Now().Unix() > info.accesstoken_expire_time {
|
||||||
|
|
||||||
|
params := url.Values{}
|
||||||
|
params.Add("client_id", mg.GoogleClientId)
|
||||||
|
params.Add("redirect_uri", mg.RedirectBaseUrl+"/authorize/"+AuthPlatformGoogle)
|
||||||
|
params.Add("client_secret", mg.GoogleClientSecret)
|
||||||
|
params.Add("scope", "https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/userinfo.email")
|
||||||
|
params.Add("refresh_token", info.token)
|
||||||
|
params.Add("grant_type", "refresh_token")
|
||||||
|
|
||||||
|
var respReferesh Google_ValidationResponse
|
||||||
|
acceestoken_expire_time := time.Now().Unix()
|
||||||
|
|
||||||
|
content := params.Encode()
|
||||||
|
resp, _ := http.Post(mg.tokenEndpoints[AuthPlatformGoogle], "application/x-www-form-urlencoded", bytes.NewBuffer([]byte(content)))
|
||||||
|
if resp != nil {
|
||||||
|
body, _ := io.ReadAll(resp.Body)
|
||||||
|
json.Unmarshal(body, &respReferesh)
|
||||||
|
|
||||||
|
// fmt.Println("==== accesstoken 업데이트 =====")
|
||||||
|
// fmt.Println("IDToken:", respReferesh.IDToken)
|
||||||
|
// fmt.Println("AccessToken:", respReferesh.AccessToken)
|
||||||
|
// fmt.Println("ExpiresIn:", respReferesh.ExpiresIn)
|
||||||
|
// fmt.Println("RefreshToken:", respReferesh.RefreshToken)
|
||||||
|
// fmt.Println("TokenType:", respReferesh.TokenType)
|
||||||
|
// fmt.Println("==============================")
|
||||||
|
|
||||||
|
acceestoken_expire_time = acceestoken_expire_time + (int64(respReferesh.ExpiresIn) * 3 / 4) //--- 3/4 이상 지나면 업데이트 한다. ms는 보통 3600초 1시간
|
||||||
|
|
||||||
|
userid, email, _ := JWTparseCode(mg.jwksUri[AuthPlatformGoogle], respReferesh.IDToken)
|
||||||
|
if userid != "" && email != "" && info.userid == userid {
|
||||||
|
info.token = respReferesh.RefreshToken
|
||||||
|
info.accesstoken = respReferesh.AccessToken
|
||||||
|
info.accesstoken_expire_time = acceestoken_expire_time
|
||||||
|
mg.setUserToken(info) //-- accesstoken 업데이트
|
||||||
|
} else {
|
||||||
|
logger.Println("JWTparseCode fail.")
|
||||||
|
return false, "", ""
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
logger.Println("tokenEndpoints fail.")
|
||||||
|
return false, "", ""
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//=================
|
||||||
|
|
||||||
|
req, _ := http.NewRequest("GET", mg.userinfoEndpoint[AuthPlatformGoogle], nil)
|
||||||
|
req.Header.Add("Authorization", "Bearer "+info.accesstoken)
|
||||||
|
|
||||||
|
client := http.Client{}
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("authorize query failed :", err)
|
||||||
|
return false, "", ""
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
var respUserInfo Google_UserInfoResponse
|
||||||
|
|
||||||
|
body, _ := io.ReadAll(resp.Body)
|
||||||
|
json.Unmarshal(body, &respUserInfo)
|
||||||
|
|
||||||
|
// fmt.Println(string(body))
|
||||||
|
// fmt.Println(respUserInfo.Sub)
|
||||||
|
// fmt.Println(respUserInfo.Email)
|
||||||
|
|
||||||
|
if respUserInfo.Sub != info.userid {
|
||||||
|
logger.Println("userinfoEndpoint fail.")
|
||||||
|
return false, "", ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, info.userid, respUserInfo.Email //?
|
||||||
|
|
||||||
|
}
|
||||||
340
core/platformmicrosoft.go
Normal file
340
core/platformmicrosoft.go
Normal file
@ -0,0 +1,340 @@
|
|||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"repositories.action2quare.com/ayo/go-ayo/logger"
|
||||||
|
|
||||||
|
"go.mongodb.org/mongo-driver/bson"
|
||||||
|
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||||
|
"go.mongodb.org/mongo-driver/mongo/options"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Microsoft_ValidationResponse struct {
|
||||||
|
IDToken string `json:"id_token"`
|
||||||
|
RefreshToken string `json:"refresh_token"`
|
||||||
|
AccessToken string `json:"access_token"`
|
||||||
|
TokenType string `json:"token_type"`
|
||||||
|
ExpiresIn int `json:"expires_in"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Microsoft_UserInfoResponse struct {
|
||||||
|
Sub string `json:"sub"`
|
||||||
|
Givenname string `json:"givenname"`
|
||||||
|
Familyname string `json:"familyname"`
|
||||||
|
Email string `json:"email"`
|
||||||
|
Locale string `json:"locale"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mg *Maingate) platform_microsoft_get_login_url(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
|
browserinfo, err := mg.GetUserBrowserInfo(r)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
logger.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
existid := r.URL.Query().Get("existid")
|
||||||
|
//fmt.Println("existid =>", existid)
|
||||||
|
if existid != "" {
|
||||||
|
//기존 계정이 있는 경우에는 그 계정 부터 조회한다.
|
||||||
|
info, err := mg.getUserTokenWithCheck(AuthPlatformMicrosoft, existid, browserinfo)
|
||||||
|
if err == nil {
|
||||||
|
if info.token != "" {
|
||||||
|
params := url.Values{}
|
||||||
|
params.Add("id", existid)
|
||||||
|
params.Add("authtype", AuthPlatformMicrosoft)
|
||||||
|
http.Redirect(w, r, "actionsquare://login?"+params.Encode(), http.StatusSeeOther)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sessionkey := mg.GeneratePlatformLoginNonceKey()
|
||||||
|
nonce := mg.GeneratePlatformLoginNonceKey()
|
||||||
|
|
||||||
|
mg.mongoClient.Delete(CollectionPlatformLoginToken, bson.M{
|
||||||
|
"platform": AuthPlatformMicrosoft,
|
||||||
|
"key": sessionkey,
|
||||||
|
})
|
||||||
|
|
||||||
|
_, _, err = mg.mongoClient.Update(CollectionPlatformLoginToken, bson.M{
|
||||||
|
"_id": primitive.NewObjectID(),
|
||||||
|
}, bson.M{
|
||||||
|
"$setOnInsert": bson.M{
|
||||||
|
"platform": AuthPlatformMicrosoft,
|
||||||
|
"key": sessionkey,
|
||||||
|
"nonce": nonce,
|
||||||
|
"brinfo": browserinfo,
|
||||||
|
},
|
||||||
|
}, options.Update().SetUpsert(true))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
logger.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
params := url.Values{}
|
||||||
|
params.Add("client_id", mg.MicrosoftClientId)
|
||||||
|
params.Add("response_type", "code")
|
||||||
|
params.Add("redirect_uri", mg.RedirectBaseUrl+"/authorize/"+AuthPlatformMicrosoft)
|
||||||
|
params.Add("response_mode", "query")
|
||||||
|
params.Add("scope", "openid offline_access https://graph.microsoft.com/mail.read")
|
||||||
|
|
||||||
|
params.Add("nonce", nonce)
|
||||||
|
|
||||||
|
// set cookie for storing token
|
||||||
|
cookie := http.Cookie{
|
||||||
|
Name: "LoginFlowContext_SessionKey",
|
||||||
|
Value: sessionkey,
|
||||||
|
Expires: time.Now().Add(1 * time.Hour),
|
||||||
|
//SameSite: http.SameSiteStrictMode,
|
||||||
|
SameSite: http.SameSiteLaxMode,
|
||||||
|
// HttpOnly: false,
|
||||||
|
Secure: true,
|
||||||
|
Path: "/",
|
||||||
|
}
|
||||||
|
http.SetCookie(w, &cookie)
|
||||||
|
|
||||||
|
//Set-Cookie
|
||||||
|
//fmt.Println(mg.authorizationEndpoints[AuthPlatformMicrosoft] + params.Encode())
|
||||||
|
|
||||||
|
//http.Redirect(w, r, "https://appleid.apple.com/auth/authorize?"+params.Encode(), http.StatusSeeOther)
|
||||||
|
http.Redirect(w, r, mg.authorizationEndpoints[AuthPlatformMicrosoft]+"?"+params.Encode(), http.StatusSeeOther)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mg *Maingate) platform_microsoft_authorize(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
|
defer r.Body.Close()
|
||||||
|
code := r.URL.Query().Get("code")
|
||||||
|
|
||||||
|
// set cookie for storing token
|
||||||
|
cookie := http.Cookie{
|
||||||
|
Name: "LoginFlowContext_code",
|
||||||
|
Value: code,
|
||||||
|
Expires: time.Now().Add(1 * time.Minute),
|
||||||
|
SameSite: http.SameSiteLaxMode,
|
||||||
|
Secure: true,
|
||||||
|
Path: "/",
|
||||||
|
}
|
||||||
|
http.SetCookie(w, &cookie)
|
||||||
|
|
||||||
|
http.Redirect(w, r, mg.RedirectBaseUrl+"/authorize_result/"+AuthPlatformMicrosoft, http.StatusSeeOther) //-- 바로 받으니까 쿠키 안와서 한번 더 Redirect 시킨다.
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mg *Maingate) platform_microsoft_authorize_result(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
|
brinfo, err := mg.GetUserBrowserInfo(r)
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
logger.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
cookie, err := r.Cookie("LoginFlowContext_SessionKey")
|
||||||
|
if err != nil {
|
||||||
|
logger.Println("Session not found", err)
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
cookiecode, err := r.Cookie("LoginFlowContext_code")
|
||||||
|
if err != nil {
|
||||||
|
logger.Println("code not found", err)
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
code := cookiecode.Value
|
||||||
|
|
||||||
|
found, err := mg.mongoClient.FindOne(CollectionPlatformLoginToken, bson.M{
|
||||||
|
"key": cookie.Value,
|
||||||
|
"platform": AuthPlatformMicrosoft,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
logger.Println("LoginFlowContext_SessionKey find key :", err)
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if found == nil {
|
||||||
|
logger.Println("LoginFlowContext_SessionKey not found")
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if cookie.Value != found["key"] {
|
||||||
|
logger.Println("LoginFlowContext_SessionKey key not match")
|
||||||
|
logger.Println(cookie.Value)
|
||||||
|
logger.Println(found["key"])
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if brinfo != found["brinfo"] { //-- 로그인 시작점과 인증점의 브라우저 혹은 접속지 정보가 다르다?
|
||||||
|
logger.Println("LoginFlowContext_SessionKey brinfo not match ")
|
||||||
|
logger.Println(brinfo)
|
||||||
|
logger.Println(found["brinfo"])
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//=================
|
||||||
|
|
||||||
|
params := url.Values{}
|
||||||
|
params.Add("client_id", mg.MicrosoftClientId)
|
||||||
|
params.Add("redirect_uri", mg.RedirectBaseUrl+"/authorize/"+AuthPlatformMicrosoft)
|
||||||
|
params.Add("code", code)
|
||||||
|
params.Add("scope", "openid offline_access https://graph.microsoft.com/mail.read")
|
||||||
|
params.Add("grant_type", "authorization_code")
|
||||||
|
|
||||||
|
params.Add("client_secret", mg.MicrosoftClientSecret)
|
||||||
|
|
||||||
|
var respReferesh Microsoft_ValidationResponse
|
||||||
|
acceestoken_expire_time := time.Now().Unix()
|
||||||
|
content := params.Encode()
|
||||||
|
resp, _ := http.Post(mg.tokenEndpoints[AuthPlatformMicrosoft], "application/x-www-form-urlencoded", bytes.NewBuffer([]byte(content)))
|
||||||
|
if resp != nil {
|
||||||
|
body, _ := io.ReadAll(resp.Body)
|
||||||
|
// w.Write(body)
|
||||||
|
//json.NewDecoder(resp.Body).Decode(respReferesh)
|
||||||
|
json.Unmarshal(body, &respReferesh)
|
||||||
|
} else {
|
||||||
|
logger.Println("tokenEndpoints fail.")
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// fmt.Println("==============================")
|
||||||
|
// fmt.Println("IDToken:", respReferesh.IDToken)
|
||||||
|
// fmt.Println("AccessToken:", respReferesh.AccessToken)
|
||||||
|
// fmt.Println("ExpiresIn:", respReferesh.ExpiresIn)
|
||||||
|
// fmt.Println("RefreshToken:", respReferesh.RefreshToken)
|
||||||
|
// fmt.Println("TokenType:", respReferesh.TokenType)
|
||||||
|
// fmt.Println("==============================")
|
||||||
|
|
||||||
|
acceestoken_expire_time = acceestoken_expire_time + (int64(respReferesh.ExpiresIn) * 3 / 4) //--- 3/4 이상 지나면 업데이트 한다. ms는 보통 3600초 1시간
|
||||||
|
|
||||||
|
userid, _, nonce := JWTparseCode(mg.jwksUri[AuthPlatformMicrosoft], respReferesh.IDToken)
|
||||||
|
|
||||||
|
if nonce == "" || nonce != found["nonce"] {
|
||||||
|
logger.Errorf("nonce not match")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if userid != "" {
|
||||||
|
var info usertokeninfo
|
||||||
|
info.platform = AuthPlatformMicrosoft
|
||||||
|
info.userid = userid
|
||||||
|
info.token = respReferesh.RefreshToken
|
||||||
|
info.brinfo = brinfo
|
||||||
|
info.accesstoken = respReferesh.AccessToken
|
||||||
|
info.accesstoken_expire_time = acceestoken_expire_time
|
||||||
|
mg.setUserToken(info)
|
||||||
|
|
||||||
|
params := url.Values{}
|
||||||
|
params.Add("id", userid)
|
||||||
|
params.Add("authtype", AuthPlatformMicrosoft)
|
||||||
|
http.Redirect(w, r, "actionsquare://login?"+params.Encode(), http.StatusSeeOther)
|
||||||
|
} else {
|
||||||
|
http.Redirect(w, r, "actionsquare://error", http.StatusSeeOther)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mg *Maingate) platform_microsoft_getuserinfo(info usertokeninfo) (bool, string, string) {
|
||||||
|
|
||||||
|
// fmt.Println(info.platform)
|
||||||
|
// fmt.Println(info.userid)
|
||||||
|
// fmt.Println(info.accesstoken)
|
||||||
|
|
||||||
|
// fmt.Println(time.Now().Unix())
|
||||||
|
// fmt.Println(info.accesstoken_expire_time)
|
||||||
|
|
||||||
|
//-- access token 갱신이 필요하다. -- userinfoEndpoint
|
||||||
|
if time.Now().Unix() > info.accesstoken_expire_time {
|
||||||
|
|
||||||
|
params := url.Values{}
|
||||||
|
params.Add("client_id", mg.MicrosoftClientId)
|
||||||
|
params.Add("redirect_uri", mg.RedirectBaseUrl+"/authorize/"+AuthPlatformMicrosoft)
|
||||||
|
params.Add("refresh_token", info.token)
|
||||||
|
params.Add("scope", "openid offline_access https://graph.microsoft.com/mail.read")
|
||||||
|
params.Add("grant_type", "refresh_token")
|
||||||
|
|
||||||
|
params.Add("client_secret", mg.MicrosoftClientSecret)
|
||||||
|
|
||||||
|
var respReferesh Microsoft_ValidationResponse
|
||||||
|
acceestoken_expire_time := time.Now().Unix()
|
||||||
|
|
||||||
|
content := params.Encode()
|
||||||
|
resp, _ := http.Post(mg.tokenEndpoints[AuthPlatformMicrosoft], "application/x-www-form-urlencoded", bytes.NewBuffer([]byte(content)))
|
||||||
|
if resp != nil {
|
||||||
|
body, _ := io.ReadAll(resp.Body)
|
||||||
|
json.Unmarshal(body, &respReferesh)
|
||||||
|
|
||||||
|
// fmt.Println("==== accesstoken 업데이트 =====")
|
||||||
|
// fmt.Println("IDToken:", respReferesh.IDToken)
|
||||||
|
// fmt.Println("AccessToken:", respReferesh.AccessToken)
|
||||||
|
// fmt.Println("ExpiresIn:", respReferesh.ExpiresIn)
|
||||||
|
// fmt.Println("RefreshToken:", respReferesh.RefreshToken)
|
||||||
|
// fmt.Println("TokenType:", respReferesh.TokenType)
|
||||||
|
// fmt.Println("==============================")
|
||||||
|
|
||||||
|
acceestoken_expire_time = acceestoken_expire_time + (int64(respReferesh.ExpiresIn) * 3 / 4) //--- 3/4 이상 지나면 업데이트 한다. ms는 보통 3600초 1시간
|
||||||
|
|
||||||
|
userid, _, _ := JWTparseCode(mg.jwksUri[AuthPlatformMicrosoft], respReferesh.IDToken)
|
||||||
|
if userid != "" && info.userid == userid {
|
||||||
|
info.token = respReferesh.RefreshToken
|
||||||
|
info.accesstoken = respReferesh.AccessToken
|
||||||
|
info.accesstoken_expire_time = acceestoken_expire_time
|
||||||
|
mg.setUserToken(info) //-- accesstoken 업데이트
|
||||||
|
} else {
|
||||||
|
logger.Println("JWTparseCode fail.")
|
||||||
|
return false, "", ""
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
logger.Println("tokenEndpoints fail.")
|
||||||
|
return false, "", ""
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//=================
|
||||||
|
|
||||||
|
req, _ := http.NewRequest("GET", mg.userinfoEndpoint[AuthPlatformMicrosoft], nil)
|
||||||
|
req.Header.Add("Authorization", "Bearer "+info.accesstoken)
|
||||||
|
|
||||||
|
client := http.Client{}
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("authorize query failed :", err)
|
||||||
|
return false, "", ""
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
var respUserInfo Microsoft_UserInfoResponse
|
||||||
|
|
||||||
|
body, _ := io.ReadAll(resp.Body)
|
||||||
|
json.Unmarshal(body, &respUserInfo)
|
||||||
|
|
||||||
|
// fmt.Println(string(body))
|
||||||
|
// fmt.Println(respUserInfo.Sub)
|
||||||
|
// fmt.Println(respUserInfo.Email)
|
||||||
|
|
||||||
|
if respUserInfo.Sub != info.userid {
|
||||||
|
logger.Println("userinfoEndpoint fail.")
|
||||||
|
return false, "", ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, info.userid, respUserInfo.Email //?
|
||||||
|
|
||||||
|
}
|
||||||
417
core/platformtwitter.go
Normal file
417
core/platformtwitter.go
Normal file
@ -0,0 +1,417 @@
|
|||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/hmac"
|
||||||
|
"crypto/sha1"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"repositories.action2quare.com/ayo/go-ayo/logger"
|
||||||
|
|
||||||
|
"go.mongodb.org/mongo-driver/bson"
|
||||||
|
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||||
|
"go.mongodb.org/mongo-driver/mongo/options"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (mg *Maingate) platform_twitter_get_login_url(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
|
browserinfo, err := mg.GetUserBrowserInfo(r)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
logger.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
existid := r.URL.Query().Get("existid")
|
||||||
|
//fmt.Println("existid =>", existid)
|
||||||
|
if existid != "" {
|
||||||
|
// 기존 계정이 있는 경우에는 그 계정 부터 조회한다.
|
||||||
|
info, err := mg.getUserTokenWithCheck(AuthPlatformTwitter, existid, browserinfo)
|
||||||
|
if err == nil {
|
||||||
|
if info.token != "" {
|
||||||
|
params := url.Values{}
|
||||||
|
params.Add("id", existid)
|
||||||
|
params.Add("authtype", AuthPlatformTwitter)
|
||||||
|
http.Redirect(w, r, "actionsquare://login?"+params.Encode(), http.StatusSeeOther)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sessionkey := mg.GeneratePlatformLoginNonceKey()
|
||||||
|
nonce := mg.GeneratePlatformLoginNonceKey()
|
||||||
|
|
||||||
|
auth_token, auth_secret := parse_TwitterOAuthToken(mg.CallTwitterAPI_WithAPPKey("https://api.twitter.com/oauth/request_token", "POST", nonce))
|
||||||
|
|
||||||
|
if auth_token == "" || auth_secret == "" {
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
} else {
|
||||||
|
|
||||||
|
mg.mongoClient.Delete(CollectionPlatformLoginToken, bson.M{
|
||||||
|
"platform": AuthPlatformTwitter,
|
||||||
|
"key": sessionkey,
|
||||||
|
})
|
||||||
|
|
||||||
|
_, _, err := mg.mongoClient.Update(CollectionPlatformLoginToken, bson.M{
|
||||||
|
"_id": primitive.NewObjectID(),
|
||||||
|
}, bson.M{
|
||||||
|
"$setOnInsert": bson.M{
|
||||||
|
"platform": AuthPlatformTwitter,
|
||||||
|
"key": sessionkey,
|
||||||
|
"token": auth_token,
|
||||||
|
"secret": auth_secret,
|
||||||
|
"nonce": nonce,
|
||||||
|
"brinfo": browserinfo,
|
||||||
|
},
|
||||||
|
}, options.Update().SetUpsert(true))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
logger.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// set cookie for storing token
|
||||||
|
cookie := http.Cookie{
|
||||||
|
Name: "LoginFlowContext_SessionKey",
|
||||||
|
Value: sessionkey,
|
||||||
|
Expires: time.Now().Add(1 * time.Hour),
|
||||||
|
//SameSite: http.SameSiteStrictMode,
|
||||||
|
SameSite: http.SameSiteLaxMode,
|
||||||
|
// HttpOnly: false,
|
||||||
|
Secure: true,
|
||||||
|
Path: "/",
|
||||||
|
}
|
||||||
|
http.SetCookie(w, &cookie)
|
||||||
|
|
||||||
|
params := url.Values{}
|
||||||
|
params.Add("oauth_token", auth_token)
|
||||||
|
http.Redirect(w, r, "https://api.twitter.com/oauth/authorize?"+params.Encode(), http.StatusSeeOther)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mg *Maingate) platform_twitter_authorize(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
|
// defer r.Body.Close()
|
||||||
|
// body, _ := io.ReadAll(r.Body)
|
||||||
|
// bodyString := string(body)
|
||||||
|
|
||||||
|
// oauth_token := r.URL.Query().Get("oauth_token")
|
||||||
|
// oauth_verifier := r.URL.Query().Get("oauth_verifier")
|
||||||
|
// fmt.Println("bodyString")
|
||||||
|
// fmt.Println(bodyString)
|
||||||
|
// fmt.Println("=======================")
|
||||||
|
// fmt.Println("Req: %s %s", r.URL.Host, r.URL.Path)
|
||||||
|
// fmt.Println(oauth_token, oauth_verifier)
|
||||||
|
// fmt.Println("=======================")
|
||||||
|
|
||||||
|
// set cookie for storing token
|
||||||
|
cookie := http.Cookie{
|
||||||
|
Name: "LoginFlowContext_code",
|
||||||
|
Value: r.URL.Query().Encode(),
|
||||||
|
Expires: time.Now().Add(1 * time.Minute),
|
||||||
|
SameSite: http.SameSiteLaxMode,
|
||||||
|
Secure: true,
|
||||||
|
Path: "/",
|
||||||
|
}
|
||||||
|
http.SetCookie(w, &cookie)
|
||||||
|
|
||||||
|
http.Redirect(w, r, mg.RedirectBaseUrl+"/authorize_result/"+AuthPlatformTwitter, http.StatusSeeOther) //-- 바로 받으니까 쿠키 안와서 한번 더 Redirect 시킨다.
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mg *Maingate) platform_twitter_authorize_result(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
|
brinfo, err := mg.GetUserBrowserInfo(r)
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
logger.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
cookie, err := r.Cookie("LoginFlowContext_SessionKey")
|
||||||
|
if err != nil {
|
||||||
|
logger.Println("Session not found", err)
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
cookiecode, err := r.Cookie("LoginFlowContext_code")
|
||||||
|
if err != nil {
|
||||||
|
logger.Println("code not found", err)
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
code := cookiecode.Value
|
||||||
|
found, err := mg.mongoClient.FindOne(CollectionPlatformLoginToken, bson.M{
|
||||||
|
"key": cookie.Value,
|
||||||
|
"platform": AuthPlatformTwitter,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
logger.Println("LoginFlowContext_SessionKey find key :", err)
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if found == nil {
|
||||||
|
logger.Println("LoginFlowContext_SessionKey not found")
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if cookie.Value != found["key"] {
|
||||||
|
logger.Println("LoginFlowContext_SessionKey key not match")
|
||||||
|
logger.Println(cookie.Value)
|
||||||
|
logger.Println(found["key"])
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if brinfo != found["brinfo"] { //-- 로그인 시작점과 인증점의 브라우저 혹은 접속지 정보가 다르다?
|
||||||
|
logger.Println("LoginFlowContext_SessionKey brinfo not match ")
|
||||||
|
logger.Println(brinfo)
|
||||||
|
logger.Println(found["brinfo"])
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
urlvalue, err := url.ParseQuery(code)
|
||||||
|
if err != nil {
|
||||||
|
logger.Println("Token parse error")
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
//===
|
||||||
|
|
||||||
|
oauth_token := urlvalue.Get("oauth_token")
|
||||||
|
oauth_verifier := urlvalue.Get("oauth_verifier")
|
||||||
|
if oauth_token == "" || oauth_verifier == "" {
|
||||||
|
w.WriteHeader(400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
userid, token, secret := getTwitterAccessToken("https://api.twitter.com/oauth/access_token?oauth_token=" + oauth_token + "&oauth_verifier=" + oauth_verifier)
|
||||||
|
|
||||||
|
if userid != "" && token != "" && secret != "" {
|
||||||
|
|
||||||
|
var info usertokeninfo
|
||||||
|
info.platform = AuthPlatformTwitter
|
||||||
|
info.userid = userid
|
||||||
|
info.token = token
|
||||||
|
info.secret = secret
|
||||||
|
info.brinfo = brinfo
|
||||||
|
|
||||||
|
mg.setUserToken(info)
|
||||||
|
params := url.Values{}
|
||||||
|
params.Add("id", userid)
|
||||||
|
params.Add("authtype", AuthPlatformTwitter)
|
||||||
|
http.Redirect(w, r, "actionsquare://login?"+params.Encode(), http.StatusSeeOther)
|
||||||
|
} else {
|
||||||
|
http.Redirect(w, r, "actionsquare://error", http.StatusSeeOther)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mg *Maingate) platform_twitter_getuserinfo(token, secret string) (bool, string, string) {
|
||||||
|
|
||||||
|
result := mg.CallTwitterAPI("https://api.twitter.com/2/users/me", "GET", token, secret, mg.GeneratePlatformLoginNonceKey())
|
||||||
|
|
||||||
|
var TwitterUserInfo struct {
|
||||||
|
Data struct {
|
||||||
|
Id string `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Username string `json:"username"`
|
||||||
|
} `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
err := json.Unmarshal([]byte(result), &TwitterUserInfo)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("twitter userinfo api unmarshal fail :", result)
|
||||||
|
return false, "", ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// fmt.Println("=====================")
|
||||||
|
// fmt.Println(result)
|
||||||
|
// fmt.Println(TwitterUserInfo.Data.Id)
|
||||||
|
// fmt.Println(TwitterUserInfo.Data.Name)
|
||||||
|
// fmt.Println(TwitterUserInfo.Data.Username)
|
||||||
|
// fmt.Println("=====================")
|
||||||
|
|
||||||
|
return true, TwitterUserInfo.Data.Id, ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mg *Maingate) CallTwitterAPI_WithAPPKey(requesturl, method, nonce string) string {
|
||||||
|
return mg.CallTwitterAPI(requesturl, method, mg.TwitterOAuthKey, mg.TwitterOAuthSecret, nonce)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mg *Maingate) CallTwitterAPI(requesturl, method, oauth_token, oauth_secret, nonce string) string {
|
||||||
|
vals := url.Values{}
|
||||||
|
|
||||||
|
if method == "GET" {
|
||||||
|
splited := strings.Split(requesturl, "?")
|
||||||
|
if len(splited) > 1 {
|
||||||
|
parameter := splited[1]
|
||||||
|
args := strings.Split(parameter, "&")
|
||||||
|
for _, arg := range args {
|
||||||
|
tempsplited := strings.Split(arg, "=")
|
||||||
|
if len(tempsplited) == 2 {
|
||||||
|
vals.Add(tempsplited[0], tempsplited[1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//vals.Add("oauth_callback", "actionclient://callback")
|
||||||
|
//vals.Add("oauth_callback", "http://127.0.0.1:7770/auth")
|
||||||
|
|
||||||
|
vals.Add("oauth_callback", mg.RedirectBaseUrl+"/authorize/"+AuthPlatformTwitter)
|
||||||
|
vals.Add("oauth_consumer_key", mg.TwitterCustomerKey)
|
||||||
|
vals.Add("oauth_token", oauth_token)
|
||||||
|
vals.Add("oauth_signature_method", "HMAC-SHA1")
|
||||||
|
vals.Add("oauth_timestamp", strconv.Itoa(int(time.Now().Unix())))
|
||||||
|
vals.Add("oauth_nonce", nonce)
|
||||||
|
vals.Add("oauth_version", "1.0")
|
||||||
|
|
||||||
|
parameterString := strings.Replace(vals.Encode(), "+", "%20", -1)
|
||||||
|
signatureBase := strings.ToUpper(method) + "&" + url.QueryEscape(strings.Split(requesturl, "?")[0]) + "&" + url.QueryEscape(parameterString)
|
||||||
|
signingKey := url.QueryEscape(mg.TwitterCustomerSecret) + "&" + url.QueryEscape(oauth_secret)
|
||||||
|
signature := calculateTwitterSignature(signatureBase, signingKey)
|
||||||
|
|
||||||
|
headerString := "OAuth oauth_callback=\"" + url.QueryEscape(vals.Get("oauth_callback")) + "\", oauth_consumer_key=\"" + url.QueryEscape(vals.Get("oauth_consumer_key")) + "\", oauth_nonce=\"" + url.QueryEscape(vals.Get("oauth_nonce")) +
|
||||||
|
"\", oauth_signature=\"" + url.QueryEscape(signature) + "\", oauth_signature_method=\"" + url.QueryEscape(vals.Get("oauth_signature_method")) +
|
||||||
|
"\", oauth_timestamp=\"" + url.QueryEscape(vals.Get("oauth_timestamp")) + "\", oauth_token=\"" + url.QueryEscape(vals.Get("oauth_token")) +
|
||||||
|
"\", oauth_version=\"" + url.QueryEscape(vals.Get("oauth_version")) + "\""
|
||||||
|
|
||||||
|
client := &http.Client{}
|
||||||
|
|
||||||
|
req, err := http.NewRequest(method, requesturl, nil)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
|
||||||
|
req.Header.Add("Content-Length", "0")
|
||||||
|
req.Header.Add("Accept-Encoding", "application/json")
|
||||||
|
req.Header.Add("Host", "api.twitter.com")
|
||||||
|
req.Header.Add("Accept", "*/*")
|
||||||
|
req.Header.Add("Authorization", headerString)
|
||||||
|
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error(err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
strBody := ""
|
||||||
|
respBody, err := io.ReadAll(resp.Body)
|
||||||
|
if err == nil {
|
||||||
|
strBody = string(respBody)
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode != 200 {
|
||||||
|
logger.Error("Error: ", resp.StatusCode, err, strBody)
|
||||||
|
return "error"
|
||||||
|
}
|
||||||
|
|
||||||
|
return strBody
|
||||||
|
}
|
||||||
|
|
||||||
|
func getTwitterAccessToken(requesturl string) (string, string, string) {
|
||||||
|
|
||||||
|
client := &http.Client{}
|
||||||
|
|
||||||
|
req, err := http.NewRequest("POST", requesturl, nil)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
|
||||||
|
req.Header.Add("Content-Length", "0")
|
||||||
|
req.Header.Add("Accept-Encoding", "application/json")
|
||||||
|
req.Header.Add("Host", "api.twitter.com")
|
||||||
|
req.Header.Add("Accept", "*/*")
|
||||||
|
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error(err)
|
||||||
|
return "", "", ""
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
strBody := ""
|
||||||
|
respBody, err := io.ReadAll(resp.Body)
|
||||||
|
if err == nil {
|
||||||
|
strBody = string(respBody)
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode != 200 {
|
||||||
|
logger.Error("Error: ", resp.StatusCode, err, strBody)
|
||||||
|
return "", "", ""
|
||||||
|
}
|
||||||
|
|
||||||
|
params := strings.Split(strBody, "&")
|
||||||
|
oauth_token := ""
|
||||||
|
oauth_secret := ""
|
||||||
|
user_id := ""
|
||||||
|
//screen_name := ""
|
||||||
|
for _, param := range params {
|
||||||
|
parsedStr := strings.Split(param, "=")
|
||||||
|
if len(parsedStr) == 2 {
|
||||||
|
if parsedStr[0] == "oauth_token" {
|
||||||
|
oauth_token = parsedStr[1]
|
||||||
|
}
|
||||||
|
if parsedStr[0] == "oauth_token_secret" {
|
||||||
|
oauth_secret = parsedStr[1]
|
||||||
|
}
|
||||||
|
if parsedStr[0] == "user_id" {
|
||||||
|
user_id = parsedStr[1]
|
||||||
|
}
|
||||||
|
// if parsedStr[0] == "screen_name" {
|
||||||
|
// screen_name = parsedStr[1]
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ret := ""
|
||||||
|
|
||||||
|
// if oauth_token != "" && oauth_secret != "" && user_id != "" && screen_name != "" {
|
||||||
|
// ret = "{ \"AuthToken\": \"" + oauth_token + "\", \"UserId\": \"" + user_id + "\", \"Screen_name\": \"" + screen_name + "\" }"
|
||||||
|
// }
|
||||||
|
// return ret, user_id, oauth_token, oauth_secret
|
||||||
|
return user_id, oauth_token, oauth_secret
|
||||||
|
}
|
||||||
|
|
||||||
|
func calculateTwitterSignature(base, key string) string {
|
||||||
|
hash := hmac.New(sha1.New, []byte(key))
|
||||||
|
hash.Write([]byte(base))
|
||||||
|
signature := hash.Sum(nil)
|
||||||
|
return base64.StdEncoding.EncodeToString(signature)
|
||||||
|
}
|
||||||
|
|
||||||
|
func parse_TwitterOAuthToken(strBody string) (string, string) {
|
||||||
|
params := strings.Split(strBody, "&")
|
||||||
|
oauth_token := ""
|
||||||
|
oauth_secret := ""
|
||||||
|
for _, param := range params {
|
||||||
|
parsedStr := strings.Split(param, "=")
|
||||||
|
|
||||||
|
if len(parsedStr) == 2 {
|
||||||
|
if parsedStr[0] == "oauth_token" {
|
||||||
|
oauth_token = parsedStr[1]
|
||||||
|
}
|
||||||
|
if parsedStr[0] == "oauth_token_secret" {
|
||||||
|
oauth_secret = parsedStr[1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return oauth_token, oauth_secret
|
||||||
|
}
|
||||||
612
core/service.go
Normal file
612
core/service.go
Normal file
@ -0,0 +1,612 @@
|
|||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/hex"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"repositories.action2quare.com/ayo/go-ayo/logger"
|
||||||
|
|
||||||
|
"repositories.action2quare.com/ayo/go-ayo/common"
|
||||||
|
|
||||||
|
"go.mongodb.org/mongo-driver/bson"
|
||||||
|
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||||
|
"go.mongodb.org/mongo-driver/mongo/options"
|
||||||
|
)
|
||||||
|
|
||||||
|
type blockinfo struct {
|
||||||
|
Start primitive.DateTime
|
||||||
|
End primitive.DateTime `bson:"_ts"`
|
||||||
|
Reason string
|
||||||
|
}
|
||||||
|
|
||||||
|
type whitelistmember struct {
|
||||||
|
Service string
|
||||||
|
Email string
|
||||||
|
Platform string
|
||||||
|
Desc string
|
||||||
|
Expired primitive.DateTime `bson:"_ts,omitempty" json:"_ts,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type whitelist struct {
|
||||||
|
emailptr unsafe.Pointer
|
||||||
|
working int32
|
||||||
|
}
|
||||||
|
|
||||||
|
type usertokeninfo struct {
|
||||||
|
platform string
|
||||||
|
userid string
|
||||||
|
token string //refreshtoken
|
||||||
|
secret string
|
||||||
|
brinfo string
|
||||||
|
accesstoken string // microsoft only
|
||||||
|
accesstoken_expire_time int64 // microsoft only
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wl *whitelist) init(total []whitelistmember) {
|
||||||
|
next := make(map[string]*whitelistmember)
|
||||||
|
for _, member := range total {
|
||||||
|
next[whitelistKey(member.Email)] = &member
|
||||||
|
}
|
||||||
|
|
||||||
|
atomic.StorePointer(&wl.emailptr, unsafe.Pointer(&next))
|
||||||
|
atomic.StoreInt32(&wl.working, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wl *whitelist) add(m *whitelistmember) {
|
||||||
|
ptr := atomic.LoadPointer(&wl.emailptr)
|
||||||
|
src := (*map[string]*whitelistmember)(ptr)
|
||||||
|
|
||||||
|
next := map[string]*whitelistmember{}
|
||||||
|
for k, v := range *src {
|
||||||
|
next[k] = v
|
||||||
|
}
|
||||||
|
next[whitelistKey(m.Email)] = m
|
||||||
|
atomic.StorePointer(&wl.emailptr, unsafe.Pointer(&next))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wl *whitelist) remove(email string) {
|
||||||
|
ptr := atomic.LoadPointer(&wl.emailptr)
|
||||||
|
src := (*map[string]*whitelistmember)(ptr)
|
||||||
|
|
||||||
|
next := make(map[string]*whitelistmember)
|
||||||
|
for k, v := range *src {
|
||||||
|
next[k] = v
|
||||||
|
}
|
||||||
|
delete(next, whitelistKey(email))
|
||||||
|
atomic.StorePointer(&wl.emailptr, unsafe.Pointer(&next))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wl *whitelist) isMember(email string, platform string) bool {
|
||||||
|
if atomic.LoadInt32(&wl.working) == 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
ptr := atomic.LoadPointer(&wl.emailptr)
|
||||||
|
src := *(*map[string]*whitelistmember)(ptr)
|
||||||
|
|
||||||
|
if member, exists := src[whitelistKey(email)]; exists {
|
||||||
|
return member.Platform == platform
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
type serviceDescription struct {
|
||||||
|
// sync.Mutex
|
||||||
|
Id primitive.ObjectID `bson:"_id"`
|
||||||
|
ServiceName string `bson:"service"`
|
||||||
|
Divisions map[string]any `bson:"divisions"`
|
||||||
|
ServiceCode string `bson:"code"`
|
||||||
|
UseWhitelist bool `bson:"use_whitelist"`
|
||||||
|
Closed bool `bson:"closed"`
|
||||||
|
ServerApiTokens []primitive.ObjectID `bson:"api_tokens"`
|
||||||
|
ApiUsers map[string][]string `bson:"api_users"`
|
||||||
|
|
||||||
|
auths *common.AuthCollection
|
||||||
|
wl whitelist
|
||||||
|
mongoClient common.MongoClient
|
||||||
|
sessionTTL time.Duration
|
||||||
|
closed int32
|
||||||
|
serviceCodeBytes []byte
|
||||||
|
getUserBrowserInfo func(r *http.Request) (string, error)
|
||||||
|
getUserTokenWithCheck func(platform string, userid string, brinfo string) (usertokeninfo, error)
|
||||||
|
updateUserinfo func(info usertokeninfo) (bool, string, string)
|
||||||
|
getProviderInfo func(platform string, uid string) (error, string, string)
|
||||||
|
|
||||||
|
apiUsers unsafe.Pointer
|
||||||
|
divisionsSerialized unsafe.Pointer
|
||||||
|
serviceSerialized unsafe.Pointer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sh *serviceDescription) readProfile(authtype string, id string, binfo string) (email string, err error) {
|
||||||
|
defer func() {
|
||||||
|
s := recover()
|
||||||
|
if s != nil {
|
||||||
|
logger.Error("readProfile failed :", authtype, id, s)
|
||||||
|
if errt, ok := s.(error); ok {
|
||||||
|
err = errt
|
||||||
|
} else {
|
||||||
|
err = errors.New(fmt.Sprint(s))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
userinfo, err := sh.getUserTokenWithCheck(authtype, id, binfo)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if userinfo.token == "" {
|
||||||
|
return "", errors.New("refreshtoken token not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
//-- 토큰으로 모두 확인이 끝났으면 갱신한다.
|
||||||
|
ok, _, email := sh.updateUserinfo(userinfo)
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
return "", errors.New("updateUserinfo failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
return email, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sh *serviceDescription) prepare(mg *Maingate) error {
|
||||||
|
div := sh.Divisions
|
||||||
|
if len(sh.ServiceCode) == 0 {
|
||||||
|
sh.ServiceCode = hex.EncodeToString(sh.Id[6:])
|
||||||
|
}
|
||||||
|
|
||||||
|
divmarshaled, _ := json.Marshal(div)
|
||||||
|
devstr := string(divmarshaled)
|
||||||
|
sh.divisionsSerialized = unsafe.Pointer(&devstr)
|
||||||
|
|
||||||
|
sh.mongoClient = mg.mongoClient
|
||||||
|
sh.auths = mg.auths
|
||||||
|
sh.sessionTTL = time.Duration(mg.SessionTTL * int64(time.Second))
|
||||||
|
sh.wl = whitelist{}
|
||||||
|
sh.serviceCodeBytes, _ = hex.DecodeString(sh.ServiceCode)
|
||||||
|
sh.getUserBrowserInfo = mg.GetUserBrowserInfo
|
||||||
|
sh.getUserTokenWithCheck = mg.getUserTokenWithCheck
|
||||||
|
sh.updateUserinfo = mg.updateUserinfo
|
||||||
|
sh.getProviderInfo = mg.getProviderInfo
|
||||||
|
|
||||||
|
if sh.Closed {
|
||||||
|
sh.closed = 1
|
||||||
|
} else {
|
||||||
|
sh.closed = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if sh.UseWhitelist {
|
||||||
|
var whites []whitelistmember
|
||||||
|
if err := mg.mongoClient.FindAllAs(CollectionWhitelist, bson.M{
|
||||||
|
"$or": []bson.M{{"service": sh.ServiceName}, {"service": sh.ServiceCode}},
|
||||||
|
}, &whites, options.Find().SetReturnKey(false)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
sh.wl.init(whites)
|
||||||
|
} else {
|
||||||
|
sh.wl.working = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(sh.ApiUsers) == 0 {
|
||||||
|
sh.ApiUsers = map[string][]string{
|
||||||
|
"service": {},
|
||||||
|
"whitelist": {},
|
||||||
|
"account": {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
parsedUsers := make(map[string]map[string]bool)
|
||||||
|
for cat, users := range sh.ApiUsers {
|
||||||
|
catusers := make(map[string]bool)
|
||||||
|
for _, user := range users {
|
||||||
|
catusers[user] = true
|
||||||
|
}
|
||||||
|
parsedUsers[cat] = catusers
|
||||||
|
}
|
||||||
|
|
||||||
|
sh.apiUsers = unsafe.Pointer(&parsedUsers)
|
||||||
|
for _, keyid := range sh.ServerApiTokens {
|
||||||
|
mg.apiTokenToService.add(keyid.Hex(), sh.ServiceCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
bt, _ := json.Marshal(sh)
|
||||||
|
atomic.StorePointer(&sh.serviceSerialized, unsafe.Pointer(&bt))
|
||||||
|
|
||||||
|
logger.Println("service is ready :", sh.ServiceName, sh.ServiceCode, sh.UseWhitelist, string(divmarshaled))
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sh *serviceDescription) link(w http.ResponseWriter, r *http.Request) {
|
||||||
|
defer func() {
|
||||||
|
s := recover()
|
||||||
|
if s != nil {
|
||||||
|
logger.Error(s)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if r.Method != "GET" {
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
queryvals := r.URL.Query()
|
||||||
|
//oldToken := queryvals.Get("otoken")
|
||||||
|
oldType := queryvals.Get("otype")
|
||||||
|
oldId := queryvals.Get("oid")
|
||||||
|
sk := queryvals.Get("sk")
|
||||||
|
//newToken := queryvals.Get("ntoken")
|
||||||
|
newType := queryvals.Get("ntype")
|
||||||
|
newId := queryvals.Get("nid")
|
||||||
|
|
||||||
|
oldAuth := sh.auths.Find(sk)
|
||||||
|
if oldAuth == nil {
|
||||||
|
// 잘못된 세션
|
||||||
|
logger.Println("link failed. session key is not valid :", sk)
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// fmt.Println("=================")
|
||||||
|
// fmt.Println(oldType)
|
||||||
|
// fmt.Println(oldId)
|
||||||
|
// fmt.Println("=================")
|
||||||
|
// fmt.Println(newType)
|
||||||
|
// fmt.Println(newId)
|
||||||
|
// fmt.Println("=================")
|
||||||
|
// fmt.Println(oldAuth.Platform)
|
||||||
|
// fmt.Println(oldAuth.Uid)
|
||||||
|
// fmt.Println("=================")
|
||||||
|
|
||||||
|
//if oldAuth.Token != oldToken || oldAuth.Uid != oldId || oldAuth.Platform != oldType {
|
||||||
|
if oldAuth.Uid != oldId || oldAuth.Platform != oldType {
|
||||||
|
logger.Println("link failed. session key is not correct :", *oldAuth, queryvals)
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
bfinfo, err := sh.getUserBrowserInfo(r)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("getUserBrowserInfo failed :", err)
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = sh.readProfile(oldType, oldId, bfinfo)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("readProfile(old) failed :", err)
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
email, err := sh.readProfile(newType, newId, bfinfo)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("readProfile(new) failed :", err)
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// if len(email) == 0 {
|
||||||
|
// logger.Println("link failed. email is missing :", r.URL.Query())
|
||||||
|
// w.WriteHeader(http.StatusBadRequest)
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
|
||||||
|
if !sh.wl.isMember(email, newType) {
|
||||||
|
logger.Println("link failed. not whitelist member :", r.URL.Query(), email)
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err, newType, newId = sh.getProviderInfo(newType, newId)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("getProviderInfo failed :", err)
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
}
|
||||||
|
|
||||||
|
createtime := primitive.NewDateTimeFromTime(time.Now().UTC())
|
||||||
|
link, err := sh.mongoClient.FindOneAndUpdate(CollectionLink, bson.M{
|
||||||
|
"platform": newType,
|
||||||
|
"uid": newId,
|
||||||
|
}, bson.M{
|
||||||
|
"$setOnInsert": bson.M{
|
||||||
|
"create": createtime,
|
||||||
|
"email": email,
|
||||||
|
},
|
||||||
|
}, options.FindOneAndUpdate().SetReturnDocument(options.After).SetUpsert(true).SetProjection(bson.M{"_id": 1}))
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("link failed. FindOneAndUpdate link err:", err)
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, newid, err := sh.mongoClient.Update(common.CollectionName(sh.ServiceName), bson.M{
|
||||||
|
"_id": link["_id"].(primitive.ObjectID),
|
||||||
|
}, bson.M{
|
||||||
|
"$setOnInsert": bson.M{
|
||||||
|
"accid": oldAuth.Accid,
|
||||||
|
"create": createtime,
|
||||||
|
},
|
||||||
|
}, options.Update().SetUpsert(true))
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("link failed. Update ServiceName err :", err)
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// newid가 있어야 한다. 그래야 기존 서비스 계정이 없는 상태이다.
|
||||||
|
if newid == nil {
|
||||||
|
// 이미 계정이 있네?
|
||||||
|
logger.Println("link failed. already have service account :", r.URL.Query())
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Println("link success :", r.URL.Query())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sh *serviceDescription) isValidAPIUser(category string, email string) bool {
|
||||||
|
ptr := atomic.LoadPointer(&sh.apiUsers)
|
||||||
|
catusers := *(*map[string]map[string]bool)(ptr)
|
||||||
|
|
||||||
|
if category == "*" {
|
||||||
|
for _, users := range catusers {
|
||||||
|
if _, ok := users[email]; ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if users, ok := catusers[category]; ok {
|
||||||
|
if _, ok := users[email]; ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Println("isValidAPIUser failed. email is not allowed :", category, email, users)
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Println("isValidAPIUser failed. category is missing :", category)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sh *serviceDescription) authorize(w http.ResponseWriter, r *http.Request) {
|
||||||
|
defer func() {
|
||||||
|
s := recover()
|
||||||
|
if s != nil {
|
||||||
|
logger.Error(s)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if r.Method != "GET" {
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
queryvals := r.URL.Query()
|
||||||
|
authtype := queryvals.Get("type")
|
||||||
|
uid := queryvals.Get("id")
|
||||||
|
//accesstoken := queryvals.Get("token") //-- 이거 이제 받지마라
|
||||||
|
session := queryvals.Get("sk")
|
||||||
|
|
||||||
|
//email, err := sh.readProfile(authtype, uid, accesstoken)
|
||||||
|
bfinfo, err := sh.getUserBrowserInfo(r)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("getUserBrowserInfo failed :", err)
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
email, err := sh.readProfile(authtype, uid, bfinfo)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("readProfile failed :", err)
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !sh.wl.isMember(email, authtype) {
|
||||||
|
logger.Println("auth failed. not whitelist member :", sh.ServiceCode, authtype, uid, email)
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Println("auth success :", authtype, uid, email, session)
|
||||||
|
|
||||||
|
err, newType, newId := sh.getProviderInfo(authtype, uid)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("getProviderInfo failed :", err)
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
}
|
||||||
|
|
||||||
|
if authtype != newType || uid != newId {
|
||||||
|
authtype = newType
|
||||||
|
uid = newId
|
||||||
|
logger.Println("auth success ( redirect ) :", authtype, uid, email, session)
|
||||||
|
}
|
||||||
|
|
||||||
|
//if len(session) == 0 && len(email) > 0 {
|
||||||
|
if len(session) == 0 {
|
||||||
|
// platform + id -> account id
|
||||||
|
createtime := primitive.NewDateTimeFromTime(time.Now().UTC())
|
||||||
|
link, err := sh.mongoClient.FindOneAndUpdate(CollectionLink, bson.M{
|
||||||
|
"platform": authtype,
|
||||||
|
"uid": uid,
|
||||||
|
}, bson.M{
|
||||||
|
"$setOnInsert": bson.M{
|
||||||
|
"create": createtime,
|
||||||
|
"email": email,
|
||||||
|
},
|
||||||
|
}, options.FindOneAndUpdate().SetReturnDocument(options.After).SetUpsert(true).SetProjection(bson.M{"_id": 1}))
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("authorize failed :", err)
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
linkid := link["_id"].(primitive.ObjectID)
|
||||||
|
newaccid := primitive.NewObjectID()
|
||||||
|
for i := 0; i < len(sh.serviceCodeBytes); i++ {
|
||||||
|
newaccid[i] ^= sh.serviceCodeBytes[i]
|
||||||
|
}
|
||||||
|
account, err := sh.mongoClient.FindOneAndUpdate(common.CollectionName(sh.ServiceName), bson.M{
|
||||||
|
"_id": linkid,
|
||||||
|
}, bson.M{
|
||||||
|
"$setOnInsert": bson.M{
|
||||||
|
"accid": newaccid,
|
||||||
|
"create": createtime,
|
||||||
|
},
|
||||||
|
}, options.FindOneAndUpdate().SetReturnDocument(options.After).SetUpsert(true).SetProjection(bson.M{"accid": 1, "create": 1}))
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("authorize failed. Update sh.ServiceName err:", err)
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
accid := account["accid"].(primitive.ObjectID)
|
||||||
|
oldcreate := account["create"].(primitive.DateTime)
|
||||||
|
newaccount := oldcreate == createtime
|
||||||
|
|
||||||
|
var bi blockinfo
|
||||||
|
if err := sh.mongoClient.FindOneAs(CollectionBlock, bson.M{
|
||||||
|
"code": sh.ServiceCode,
|
||||||
|
"accid": accid,
|
||||||
|
}, &bi); err != nil {
|
||||||
|
logger.Error("authorize failed. find blockinfo in CollectionBlock err:", err)
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !bi.Start.Time().IsZero() {
|
||||||
|
now := time.Now().UTC()
|
||||||
|
if bi.Start.Time().Before(now) && bi.End.Time().After(now) {
|
||||||
|
// block됐네?
|
||||||
|
// status는 정상이고 reason을 넘겨주자
|
||||||
|
json.NewEncoder(w).Encode(map[string]any{
|
||||||
|
"blocked": bi,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
newsession := primitive.NewObjectID()
|
||||||
|
expired := primitive.NewDateTimeFromTime(time.Now().UTC().Add(sh.sessionTTL))
|
||||||
|
newauth := common.Authinfo{
|
||||||
|
Accid: accid,
|
||||||
|
ServiceCode: sh.ServiceCode,
|
||||||
|
Platform: authtype,
|
||||||
|
Uid: uid,
|
||||||
|
//Token: accesstoken,
|
||||||
|
Sk: newsession,
|
||||||
|
Expired: expired,
|
||||||
|
//RefreshToken: queryvals.Get("rt"),
|
||||||
|
}
|
||||||
|
|
||||||
|
_, _, err = sh.mongoClient.UpsertOne(CollectionAuth, bson.M{"_id": newauth.Accid}, &newauth)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("authorize failed :", err)
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
output := map[string]any{
|
||||||
|
"sk": newsession.Hex(),
|
||||||
|
"expirein": sh.sessionTTL.Seconds(),
|
||||||
|
"newAccount": newaccount,
|
||||||
|
"accid": newauth.Accid.Hex(),
|
||||||
|
}
|
||||||
|
bt, _ := json.Marshal(output)
|
||||||
|
w.Write(bt)
|
||||||
|
} else if len(session) > 0 {
|
||||||
|
sessionobj, _ := primitive.ObjectIDFromHex(session)
|
||||||
|
if !sessionobj.IsZero() {
|
||||||
|
updated, _, err := sh.mongoClient.Update(CollectionAuth,
|
||||||
|
bson.M{
|
||||||
|
"sk": sessionobj,
|
||||||
|
},
|
||||||
|
bson.M{
|
||||||
|
"$currentDate": bson.M{
|
||||||
|
"_ts": bson.M{"$type": "date"},
|
||||||
|
},
|
||||||
|
}, options.Update().SetUpsert(false))
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("update auth collection failed")
|
||||||
|
logger.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !updated {
|
||||||
|
// 세션이 없네?
|
||||||
|
logger.Println("authorize failed. session not exists in database :", session)
|
||||||
|
w.WriteHeader(http.StatusUnauthorized)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
output := map[string]any{
|
||||||
|
"sk": session,
|
||||||
|
"expirein": sh.sessionTTL.Seconds(),
|
||||||
|
}
|
||||||
|
bt, _ := json.Marshal(output)
|
||||||
|
w.Write(bt)
|
||||||
|
} else {
|
||||||
|
logger.Println("authorize failed. sk is not valid hex :", session)
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger.Println("authorize failed. id empty :", queryvals)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sh *serviceDescription) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
|
defer func() {
|
||||||
|
s := recover()
|
||||||
|
if s != nil {
|
||||||
|
logger.Error(s)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
io.Copy(io.Discard, r.Body)
|
||||||
|
r.Body.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
|
if atomic.LoadInt32(&sh.closed) != 0 {
|
||||||
|
w.WriteHeader(http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.HasSuffix(r.URL.Path, "/auth") {
|
||||||
|
sh.authorize(w, r)
|
||||||
|
} else if strings.HasSuffix(r.URL.Path, "/link") {
|
||||||
|
sh.link(w, r)
|
||||||
|
} else {
|
||||||
|
// TODO : 세션키와 authtoken을 헤더로 받아서 accid 조회
|
||||||
|
queryvals := r.URL.Query()
|
||||||
|
//token := queryvals.Get("token")
|
||||||
|
token := "" // 더이상 쓰지 않는다.
|
||||||
|
sk := queryvals.Get("sk")
|
||||||
|
|
||||||
|
//if len(token) == 0 || len(sk) == 0 {
|
||||||
|
if len(sk) == 0 {
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO : 각 서버에 있는 자산? 캐릭터 정보를 보여줘야 하나. 뭘 보여줄지는 프로젝트에 문의
|
||||||
|
// 일단 서버 종류만 내려보내자
|
||||||
|
// 세션키가 있는지 확인
|
||||||
|
if _, ok := sh.auths.IsValid(sk, token); !ok {
|
||||||
|
logger.Println("sessionkey is not valid :", sk, token)
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
divstrptr := atomic.LoadPointer(&sh.divisionsSerialized)
|
||||||
|
divstr := *(*string)(divstrptr)
|
||||||
|
w.Write([]byte(divstr))
|
||||||
|
}
|
||||||
|
}
|
||||||
330
core/watch.go
Normal file
330
core/watch.go
Normal file
@ -0,0 +1,330 @@
|
|||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"repositories.action2quare.com/ayo/go-ayo/logger"
|
||||||
|
|
||||||
|
"repositories.action2quare.com/ayo/go-ayo/common"
|
||||||
|
|
||||||
|
"go.mongodb.org/mongo-driver/bson"
|
||||||
|
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||||
|
"go.mongodb.org/mongo-driver/mongo"
|
||||||
|
"go.mongodb.org/mongo-driver/mongo/options"
|
||||||
|
)
|
||||||
|
|
||||||
|
type authPipelineDocument struct {
|
||||||
|
OperationType string `bson:"operationType"`
|
||||||
|
DocumentKey struct {
|
||||||
|
Id primitive.ObjectID `bson:"_id"`
|
||||||
|
} `bson:"documentKey"`
|
||||||
|
Authinfo *common.Authinfo `bson:"fullDocument"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type servicePipelineDocument struct {
|
||||||
|
OperationType string `bson:"operationType"`
|
||||||
|
DocumentKey struct {
|
||||||
|
Id primitive.ObjectID `bson:"_id"`
|
||||||
|
} `bson:"documentKey"`
|
||||||
|
Service *serviceDescription `bson:"fullDocument"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type whilelistPipelineDocument struct {
|
||||||
|
OperationType string `bson:"operationType"`
|
||||||
|
DocumentKey struct {
|
||||||
|
Id primitive.ObjectID `bson:"_id"`
|
||||||
|
} `bson:"documentKey"`
|
||||||
|
Member *whitelistmember `bson:"fullDocument"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mg *Maingate) watchWhitelistCollection(parentctx context.Context) {
|
||||||
|
defer func() {
|
||||||
|
s := recover()
|
||||||
|
if s != nil {
|
||||||
|
logger.Error(s)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
matchStage := bson.D{
|
||||||
|
{
|
||||||
|
Key: "$match", Value: bson.D{
|
||||||
|
{Key: "operationType", Value: bson.D{
|
||||||
|
{Key: "$in", Value: bson.A{
|
||||||
|
"update",
|
||||||
|
"insert",
|
||||||
|
}},
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
projectStage := bson.D{
|
||||||
|
{
|
||||||
|
Key: "$project", Value: bson.D{
|
||||||
|
{Key: "documentKey", Value: 1},
|
||||||
|
{Key: "operationType", Value: 1},
|
||||||
|
{Key: "fullDocument", Value: 1},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var stream *mongo.ChangeStream
|
||||||
|
var err error
|
||||||
|
var ctx context.Context
|
||||||
|
|
||||||
|
for {
|
||||||
|
if stream == nil {
|
||||||
|
stream, err = mg.mongoClient.Watch(CollectionWhitelist, mongo.Pipeline{matchStage, projectStage})
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("watchWhitelistCollection watch failed :", err)
|
||||||
|
time.Sleep(time.Minute)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ctx = context.TODO()
|
||||||
|
}
|
||||||
|
|
||||||
|
changed := stream.TryNext(ctx)
|
||||||
|
if ctx.Err() != nil {
|
||||||
|
logger.Error("watchServiceCollection stream.TryNext failed. process should be restarted! :", ctx.Err().Error())
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if changed {
|
||||||
|
var data whilelistPipelineDocument
|
||||||
|
if err := stream.Decode(&data); err == nil {
|
||||||
|
ot := data.OperationType
|
||||||
|
switch ot {
|
||||||
|
case "insert":
|
||||||
|
// 새 화이트리스트 멤버
|
||||||
|
if svc := mg.services.get(data.Member.Service); svc != nil {
|
||||||
|
svc.wl.add(data.Member)
|
||||||
|
}
|
||||||
|
case "update":
|
||||||
|
if svc := mg.services.get(data.Member.Service); svc != nil {
|
||||||
|
if data.Member.Expired != 0 {
|
||||||
|
logger.Println("whitelist member is removed :", *data.Member)
|
||||||
|
svc.wl.remove(data.Member.Email)
|
||||||
|
} else {
|
||||||
|
logger.Println("whitelist member is updated :", *data.Member)
|
||||||
|
svc.wl.add(data.Member)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger.Error("watchServiceCollection stream.Decode failed :", err)
|
||||||
|
}
|
||||||
|
} else if stream.Err() != nil || stream.ID() == 0 {
|
||||||
|
logger.Error("watchServiceCollection stream error :", stream.Err())
|
||||||
|
stream.Close(ctx)
|
||||||
|
stream = nil
|
||||||
|
} else {
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mg *Maingate) watchServiceCollection(parentctx context.Context, serveMux *http.ServeMux, prefix string) {
|
||||||
|
defer func() {
|
||||||
|
s := recover()
|
||||||
|
if s != nil {
|
||||||
|
logger.Error(s)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
matchStage := bson.D{
|
||||||
|
{
|
||||||
|
Key: "$match", Value: bson.D{
|
||||||
|
{Key: "operationType", Value: bson.D{
|
||||||
|
{Key: "$in", Value: bson.A{
|
||||||
|
"delete",
|
||||||
|
"insert",
|
||||||
|
"update",
|
||||||
|
"replace",
|
||||||
|
}},
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
projectStage := bson.D{
|
||||||
|
{
|
||||||
|
Key: "$project", Value: bson.D{
|
||||||
|
{Key: "operationType", Value: 1},
|
||||||
|
{Key: "fullDocument", Value: 1},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var stream *mongo.ChangeStream
|
||||||
|
var err error
|
||||||
|
var ctx context.Context
|
||||||
|
|
||||||
|
for {
|
||||||
|
if stream == nil {
|
||||||
|
stream, err = mg.mongoClient.Watch(CollectionService, mongo.Pipeline{matchStage, projectStage}, options.ChangeStream().SetFullDocument(options.UpdateLookup))
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("watchServiceCollection watch failed :", err)
|
||||||
|
time.Sleep(time.Minute)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ctx = context.TODO()
|
||||||
|
}
|
||||||
|
|
||||||
|
changed := stream.TryNext(ctx)
|
||||||
|
if ctx.Err() != nil {
|
||||||
|
logger.Error("watchServiceCollection stream.TryNext failed. process should be restarted! :", ctx.Err().Error())
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if changed {
|
||||||
|
var data servicePipelineDocument
|
||||||
|
if err := stream.Decode(&data); err == nil {
|
||||||
|
ot := data.OperationType
|
||||||
|
switch ot {
|
||||||
|
case "insert":
|
||||||
|
// 새 서비스 추가됨
|
||||||
|
if err := data.Service.prepare(mg); err != nil {
|
||||||
|
logger.Error("service cannot be prepared :", data.Service, err)
|
||||||
|
} else {
|
||||||
|
logger.Println("service is on the board! :", data.Service)
|
||||||
|
mg.services.add(data.Service)
|
||||||
|
serveMux.Handle(common.MakeHttpHandlerPattern(prefix, data.Service.ServiceCode, "/"), data.Service)
|
||||||
|
}
|
||||||
|
|
||||||
|
case "replace":
|
||||||
|
fallthrough
|
||||||
|
|
||||||
|
case "update":
|
||||||
|
data.Service.prepare(mg)
|
||||||
|
if old := mg.services.get(data.Service.ServiceName); old != nil {
|
||||||
|
logger.Printf("service is changed : %v", data.Service)
|
||||||
|
|
||||||
|
atomic.SwapPointer(&old.divisionsSerialized, data.Service.divisionsSerialized)
|
||||||
|
atomic.SwapPointer(&old.apiUsers, data.Service.apiUsers)
|
||||||
|
atomic.SwapPointer(&old.serviceSerialized, data.Service.serviceSerialized)
|
||||||
|
|
||||||
|
for _, token := range old.ServerApiTokens {
|
||||||
|
mg.apiTokenToService.remove(token.Hex())
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, token := range data.Service.ServerApiTokens {
|
||||||
|
mg.apiTokenToService.add(token.Hex(), data.Service.ServiceCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
if data.Service.UseWhitelist {
|
||||||
|
atomic.StoreInt32(&old.wl.working, 1)
|
||||||
|
} else {
|
||||||
|
atomic.StoreInt32(&old.wl.working, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
old.Closed = data.Service.Closed
|
||||||
|
if old.Closed {
|
||||||
|
atomic.StoreInt32(&old.closed, 1)
|
||||||
|
} else {
|
||||||
|
atomic.StoreInt32(&old.closed, 0)
|
||||||
|
}
|
||||||
|
atomic.SwapPointer(&old.wl.emailptr, data.Service.wl.emailptr)
|
||||||
|
|
||||||
|
old.Divisions = data.Service.Divisions
|
||||||
|
} else if !data.Service.Closed {
|
||||||
|
if err := data.Service.prepare(mg); err != nil {
|
||||||
|
logger.Error("service cannot be prepared :", data.Service, err)
|
||||||
|
} else {
|
||||||
|
logger.Println("service is on the board! :", data.Service)
|
||||||
|
mg.services.add(data.Service)
|
||||||
|
serveMux.Handle(common.MakeHttpHandlerPattern(prefix, data.Service.ServiceCode, "/"), data.Service)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "delete":
|
||||||
|
if deleted := mg.services.remove(data.DocumentKey.Id); deleted != nil {
|
||||||
|
logger.Println("service is closed :", data.Service)
|
||||||
|
atomic.AddInt32(&deleted.closed, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger.Error("watchServiceCollection stream.Decode failed :", err)
|
||||||
|
}
|
||||||
|
} else if stream.Err() != nil || stream.ID() == 0 {
|
||||||
|
logger.Error("watchServiceCollection stream error :", stream.Err())
|
||||||
|
stream.Close(ctx)
|
||||||
|
stream = nil
|
||||||
|
} else {
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func watchAuthCollection(parentctx context.Context, ac *common.AuthCollection, mongoClient common.MongoClient) {
|
||||||
|
defer func() {
|
||||||
|
s := recover()
|
||||||
|
if s != nil {
|
||||||
|
logger.Error(s)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
matchStage := bson.D{
|
||||||
|
{
|
||||||
|
Key: "$match", Value: bson.D{
|
||||||
|
{Key: "operationType", Value: bson.D{
|
||||||
|
{Key: "$in", Value: bson.A{
|
||||||
|
"delete",
|
||||||
|
"insert",
|
||||||
|
"update",
|
||||||
|
}},
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
projectStage := bson.D{
|
||||||
|
{
|
||||||
|
Key: "$project", Value: bson.D{
|
||||||
|
{Key: "documentKey", Value: 1},
|
||||||
|
{Key: "operationType", Value: 1},
|
||||||
|
{Key: "fullDocument", Value: 1},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var stream *mongo.ChangeStream
|
||||||
|
var err error
|
||||||
|
var ctx context.Context
|
||||||
|
|
||||||
|
for {
|
||||||
|
if stream == nil {
|
||||||
|
stream, err = mongoClient.Watch(CollectionAuth, mongo.Pipeline{matchStage, projectStage})
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("watchAuthCollection watch failed :", err)
|
||||||
|
time.Sleep(time.Minute)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ctx = context.TODO()
|
||||||
|
}
|
||||||
|
|
||||||
|
changed := stream.TryNext(ctx)
|
||||||
|
if ctx.Err() != nil {
|
||||||
|
logger.Error("watchAuthCollection stream.TryNext failed. process should be restarted! :", ctx.Err().Error())
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if changed {
|
||||||
|
var data authPipelineDocument
|
||||||
|
if err := stream.Decode(&data); err == nil {
|
||||||
|
ot := data.OperationType
|
||||||
|
switch ot {
|
||||||
|
case "insert":
|
||||||
|
ac.AddRaw(&mongoAuthCell{src: data.Authinfo})
|
||||||
|
case "update":
|
||||||
|
ac.AddRaw(&mongoAuthCell{src: data.Authinfo})
|
||||||
|
case "delete":
|
||||||
|
ac.RemoveByAccId(data.DocumentKey.Id)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger.Error("watchAuthCollection stream.Decode failed :", err)
|
||||||
|
}
|
||||||
|
} else if stream.Err() != nil || stream.ID() == 0 {
|
||||||
|
logger.Error("watchAuthCollection stream error :", stream.Err())
|
||||||
|
stream.Close(ctx)
|
||||||
|
stream = nil
|
||||||
|
} else {
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
55
go.mod
Normal file
55
go.mod
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
module repositories.action2quare.com/ayo/maingate
|
||||||
|
|
||||||
|
go 1.19
|
||||||
|
|
||||||
|
require (
|
||||||
|
firebase.google.com/go v3.13.0+incompatible
|
||||||
|
github.com/golang-jwt/jwt v3.2.2+incompatible
|
||||||
|
go.mongodb.org/mongo-driver v1.11.6
|
||||||
|
google.golang.org/api v0.124.0
|
||||||
|
repositories.action2quare.com/ayo/go-ayo v0.0.0-20230524030148-5cf64e74bbfa
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
cloud.google.com/go v0.110.0 // indirect
|
||||||
|
cloud.google.com/go/compute v1.19.0 // indirect
|
||||||
|
cloud.google.com/go/compute/metadata v0.2.3 // indirect
|
||||||
|
cloud.google.com/go/firestore v1.9.0 // indirect
|
||||||
|
cloud.google.com/go/iam v0.13.0 // indirect
|
||||||
|
cloud.google.com/go/longrunning v0.4.1 // indirect
|
||||||
|
cloud.google.com/go/storage v1.28.1 // indirect
|
||||||
|
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||||
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||||
|
github.com/go-redis/redis/v8 v8.11.5 // indirect
|
||||||
|
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect
|
||||||
|
github.com/golang/protobuf v1.5.3 // indirect
|
||||||
|
github.com/golang/snappy v0.0.1 // indirect
|
||||||
|
github.com/google/go-cmp v0.5.9 // indirect
|
||||||
|
github.com/google/s2a-go v0.1.4 // indirect
|
||||||
|
github.com/google/uuid v1.3.0 // indirect
|
||||||
|
github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect
|
||||||
|
github.com/googleapis/gax-go/v2 v2.8.0 // indirect
|
||||||
|
github.com/klauspost/compress v1.13.6 // indirect
|
||||||
|
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe // indirect
|
||||||
|
github.com/pires/go-proxyproto v0.7.0 // indirect
|
||||||
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
|
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
|
||||||
|
github.com/xdg-go/scram v1.1.1 // indirect
|
||||||
|
github.com/xdg-go/stringprep v1.0.3 // indirect
|
||||||
|
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect
|
||||||
|
go.opencensus.io v0.24.0 // indirect
|
||||||
|
golang.org/x/crypto v0.9.0 // indirect
|
||||||
|
golang.org/x/net v0.10.0 // indirect
|
||||||
|
golang.org/x/oauth2 v0.8.0 // indirect
|
||||||
|
golang.org/x/sync v0.2.0 // indirect
|
||||||
|
golang.org/x/sys v0.8.0 // indirect
|
||||||
|
golang.org/x/text v0.9.0 // indirect
|
||||||
|
golang.org/x/time v0.1.0 // indirect
|
||||||
|
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
|
||||||
|
google.golang.org/appengine v1.6.7 // indirect
|
||||||
|
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect
|
||||||
|
google.golang.org/grpc v1.55.0 // indirect
|
||||||
|
google.golang.org/protobuf v1.30.0 // indirect
|
||||||
|
)
|
||||||
|
|
||||||
|
replace repositories.action2quare.com/ayo/maingate => ./
|
||||||
260
go.sum
Normal file
260
go.sum
Normal file
@ -0,0 +1,260 @@
|
|||||||
|
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||||
|
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||||
|
cloud.google.com/go v0.110.0 h1:Zc8gqp3+a9/Eyph2KDmcGaPtbKRIoqq4YTlL4NMD0Ys=
|
||||||
|
cloud.google.com/go v0.110.0/go.mod h1:SJnCLqQ0FCFGSZMUNUf84MV3Aia54kn7pi8st7tMzaY=
|
||||||
|
cloud.google.com/go/compute v1.19.0 h1:+9zda3WGgW1ZSTlVppLCYFIr48Pa35q1uG2N1itbCEQ=
|
||||||
|
cloud.google.com/go/compute v1.19.0/go.mod h1:rikpw2y+UMidAe9tISo04EHNOIf42RLYF/q8Bs93scU=
|
||||||
|
cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY=
|
||||||
|
cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA=
|
||||||
|
cloud.google.com/go/firestore v1.9.0 h1:IBlRyxgGySXu5VuW0RgGFlTtLukSnNkpDiEOMkQkmpA=
|
||||||
|
cloud.google.com/go/firestore v1.9.0/go.mod h1:HMkjKHNTtRyZNiMzu7YAsLr9K3X2udY2AMwDaMEQiiE=
|
||||||
|
cloud.google.com/go/iam v0.13.0 h1:+CmB+K0J/33d0zSQ9SlFWUeCCEn5XJA0ZMZ3pHE9u8k=
|
||||||
|
cloud.google.com/go/iam v0.13.0/go.mod h1:ljOg+rcNfzZ5d6f1nAUJ8ZIxOaZUVoS14bKCtaLZ/D0=
|
||||||
|
cloud.google.com/go/longrunning v0.4.1 h1:v+yFJOfKC3yZdY6ZUI933pIYdhyhV8S3NpWrXWmg7jM=
|
||||||
|
cloud.google.com/go/longrunning v0.4.1/go.mod h1:4iWDqhBZ70CvZ6BfETbvam3T8FMvLK+eFj0E6AaRQTo=
|
||||||
|
cloud.google.com/go/storage v1.28.1 h1:F5QDG5ChchaAVQhINh24U99OWHURqrW8OmQcGKXcbgI=
|
||||||
|
cloud.google.com/go/storage v1.28.1/go.mod h1:Qnisd4CqDdo6BGs2AD5LLnEsmSQ80wQ5ogcBBKhU86Y=
|
||||||
|
firebase.google.com/go v3.13.0+incompatible h1:3TdYC3DDi6aHn20qoRkxwGqNgdjtblwVAyRLQwGn/+4=
|
||||||
|
firebase.google.com/go v3.13.0+incompatible/go.mod h1:xlah6XbEyW6tbfSklcfe5FHJIwjt8toICdV5Wh9ptHs=
|
||||||
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
|
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
||||||
|
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||||
|
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
|
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
||||||
|
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
|
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||||
|
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||||
|
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||||
|
github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=
|
||||||
|
github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||||
|
github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||||
|
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||||
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||||
|
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||||
|
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||||
|
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||||
|
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
||||||
|
github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
|
||||||
|
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||||
|
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
|
||||||
|
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||||
|
github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
|
||||||
|
github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
|
||||||
|
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
|
||||||
|
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
|
||||||
|
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||||
|
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY=
|
||||||
|
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||||
|
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||||
|
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||||
|
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||||
|
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||||
|
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||||
|
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||||
|
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||||
|
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||||
|
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||||
|
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||||
|
github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
|
||||||
|
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||||
|
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||||
|
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
|
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
|
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||||
|
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
|
github.com/google/martian/v3 v3.3.2 h1:IqNFLAmvJOgVlpdEBiQbDc2EwKW77amAycfTuWKdfvw=
|
||||||
|
github.com/google/s2a-go v0.1.4 h1:1kZ/sQM3srePvKs3tXAvQzo66XfcReoqFpIpIccE7Oc=
|
||||||
|
github.com/google/s2a-go v0.1.4/go.mod h1:Ej+mSEMGRnqRzjc7VtF+jdBwYG5fuJfiZ8ELkjEwM0A=
|
||||||
|
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||||
|
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/googleapis/enterprise-certificate-proxy v0.2.3 h1:yk9/cqRKtT9wXZSsRH9aurXEpJX+U6FLtpYTdC3R06k=
|
||||||
|
github.com/googleapis/enterprise-certificate-proxy v0.2.3/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k=
|
||||||
|
github.com/googleapis/gax-go/v2 v2.8.0 h1:UBtEZqx1bjXtOQ5BVTkuYghXrr3N4V123VKJK67vJZc=
|
||||||
|
github.com/googleapis/gax-go/v2 v2.8.0/go.mod h1:4orTrqY6hXxxaUL4LHIPl6lGo8vAE38/qKbhSAKP6QI=
|
||||||
|
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
|
||||||
|
github.com/klauspost/compress v1.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQxcgc=
|
||||||
|
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
|
||||||
|
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||||
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
|
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||||
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
|
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe h1:iruDEfMl2E6fbMZ9s0scYfZQ84/6SPL6zC8ACM2oIL0=
|
||||||
|
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
|
||||||
|
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
|
||||||
|
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
|
||||||
|
github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE=
|
||||||
|
github.com/pires/go-proxyproto v0.7.0 h1:IukmRewDQFWC7kfnb66CSomk2q/seBuilHBYFwyq0Hs=
|
||||||
|
github.com/pires/go-proxyproto v0.7.0/go.mod h1:Vz/1JPY/OACxWGQNIRY2BeyDmpoaWmEP40O9LbuiFR4=
|
||||||
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||||
|
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
|
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||||
|
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
|
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
||||||
|
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
|
github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4=
|
||||||
|
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
|
||||||
|
github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
|
||||||
|
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
|
||||||
|
github.com/xdg-go/scram v1.1.1 h1:VOMT+81stJgXW3CpHyqHN3AXDYIMsx56mEFrB37Mb/E=
|
||||||
|
github.com/xdg-go/scram v1.1.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23ni57g=
|
||||||
|
github.com/xdg-go/stringprep v1.0.3 h1:kdwGpVNwPFtjs98xCGkHjQtGKh86rDcRZN17QEMCOIs=
|
||||||
|
github.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgklLGvcBnW8=
|
||||||
|
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d h1:splanxYIlg+5LfHAM6xpdFEAYOk8iySO56hMFq6uLyA=
|
||||||
|
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA=
|
||||||
|
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||||
|
go.mongodb.org/mongo-driver v1.11.6 h1:XM7G6PjiGAO5betLF13BIa5TlLUUE3uJ/2Ox3Lz1K+o=
|
||||||
|
go.mongodb.org/mongo-driver v1.11.6/go.mod h1:G9TgswdsWjX4tmDA5zfs2+6AEPpYJwqblyjsfuh8oXY=
|
||||||
|
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
|
||||||
|
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
|
||||||
|
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
|
||||||
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
|
golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
|
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
|
golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g=
|
||||||
|
golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
|
||||||
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
|
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
|
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||||
|
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||||
|
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||||
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||||
|
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
|
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
|
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
|
golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
|
||||||
|
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
|
golang.org/x/oauth2 v0.8.0 h1:6dkIjl3j3LtZ/O3sTgZTMsLKSftL/B8Zgq4huOIIUu8=
|
||||||
|
golang.org/x/oauth2 v0.8.0/go.mod h1:yr7u4HXZRm1R1kBWqr/xKNqewf0plRYoB7sla+BCIXE=
|
||||||
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI=
|
||||||
|
golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
|
||||||
|
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
|
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||||
|
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
|
||||||
|
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||||
|
golang.org/x/time v0.1.0 h1:xYY+Bajn2a7VBmTM5GikTmnK8ZuX8YgnQCqZpbBNtmA=
|
||||||
|
golang.org/x/time v0.1.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||||
|
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
|
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||||
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||||
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk=
|
||||||
|
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
|
||||||
|
google.golang.org/api v0.124.0 h1:dP6Ef1VgOGqQ8eiv4GiY8RhmeyqzovcXBYPDUYG8Syo=
|
||||||
|
google.golang.org/api v0.124.0/go.mod h1:xu2HQurE5gi/3t1aFCvhPD781p0a3p11sdunTJ2BlP4=
|
||||||
|
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||||
|
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
|
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
|
||||||
|
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||||
|
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||||
|
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||||
|
google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||||
|
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||||
|
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 h1:KpwkzHKEF7B9Zxg18WzOa7djJ+Ha5DzthMyZYQfEn2A=
|
||||||
|
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU=
|
||||||
|
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||||
|
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||||
|
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||||
|
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||||
|
google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
|
||||||
|
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
|
||||||
|
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
||||||
|
google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ=
|
||||||
|
google.golang.org/grpc v1.55.0 h1:3Oj82/tFSCeUrRTg/5E/7d/W5A1tj6Ky1ABAuZuv5ag=
|
||||||
|
google.golang.org/grpc v1.55.0/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGONTY8=
|
||||||
|
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||||
|
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||||
|
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||||
|
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||||
|
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||||
|
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||||
|
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||||
|
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||||
|
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||||
|
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||||
|
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||||
|
google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
|
||||||
|
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||||
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
repositories.action2quare.com/ayo/go-ayo v0.0.0-20230524030148-5cf64e74bbfa h1:mlRZAjs1adM8UVad00SQ2tWvhCzZqbKcHmAyjc4NnjY=
|
||||||
|
repositories.action2quare.com/ayo/go-ayo v0.0.0-20230524030148-5cf64e74bbfa/go.mod h1:AKV0q5x39cg3+l7be0B2QaQhHGcBf+eLdlcWd1lgs70=
|
||||||
48
main.go
Normal file
48
main.go
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"flag"
|
||||||
|
"math/rand"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"repositories.action2quare.com/ayo/maingate/core"
|
||||||
|
|
||||||
|
"repositories.action2quare.com/ayo/go-ayo/common"
|
||||||
|
"repositories.action2quare.com/ayo/go-ayo/logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
// linux : go build --ldflags="-X 'main.revision=$(git rev-parse --short HEAD)'"
|
||||||
|
// windows : for /f usebackq %F in (`git rev-parse --short HEAD`) do go build --ldflags="-X 'main.revision=%F'"
|
||||||
|
var revision = "0000000"
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
if !flag.Parsed() {
|
||||||
|
flag.Parse()
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Println("build revision =", revision)
|
||||||
|
rand.Seed(time.Now().UnixNano())
|
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
mg, err := core.New(ctx)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("core.New failed :", err)
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
serveMux := http.NewServeMux()
|
||||||
|
if err := mg.RegisterHandlers(ctx, serveMux, *common.PrefixPtr); err != nil {
|
||||||
|
logger.Error("RegisterHandlers failed :", err)
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
server := common.NewHTTPServer(serveMux)
|
||||||
|
logger.Println("maingate is started")
|
||||||
|
if err := server.Start(); err != nil {
|
||||||
|
logger.Error("maingate is stopped with error :", err)
|
||||||
|
}
|
||||||
|
cancel()
|
||||||
|
mg.Destructor()
|
||||||
|
}
|
||||||
223
www/gamepot.html
Normal file
223
www/gamepot.html
Normal file
@ -0,0 +1,223 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>GamePot JS SDK Sandbox</title>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<!-- <meta
|
||||||
|
name="viewport"
|
||||||
|
content="width=device-width, initial-scale=1, shrink-to-fit=no"
|
||||||
|
/> -->
|
||||||
|
|
||||||
|
<!-- <link
|
||||||
|
href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css"
|
||||||
|
rel="stylesheet"
|
||||||
|
integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh"
|
||||||
|
crossorigin="anonymous"
|
||||||
|
/> -->
|
||||||
|
<!-- <script src="https://gamepot.gcdn.ntruss.com/gamepot-sdk-javascript-lastest.min.js"></script> -->
|
||||||
|
<script src="https://cdn.gamepot.io/dev/gamepot-sdk-javascript-1.0.19-b1.min.js"></script>
|
||||||
|
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
|
||||||
|
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body class="container-fluid" style="margin-bottom: 200px;">
|
||||||
|
<script type="text/javascript">
|
||||||
|
|
||||||
|
function onLoginsuccess(user) {
|
||||||
|
// -- 로그인 성공
|
||||||
|
// id: 회원 아이디
|
||||||
|
// token: 로그인 토큰(JWT)
|
||||||
|
// nickname: 닉네임
|
||||||
|
// provider: 소셜 로그인 종류
|
||||||
|
// providerId: 소셜 로그인 ID
|
||||||
|
// verify: 인증여부
|
||||||
|
// agree: 약관 동의 여부
|
||||||
|
//===========================
|
||||||
|
|
||||||
|
const formauth = document.createElement('form');
|
||||||
|
let objs1 = document.createElement('input');
|
||||||
|
objs1.setAttribute('type', 'hidden');
|
||||||
|
objs1.setAttribute('name', 'state');
|
||||||
|
objs1.setAttribute('value', '{{ .State }}');
|
||||||
|
formauth.appendChild(objs1);
|
||||||
|
|
||||||
|
let objs2 = document.createElement('input');
|
||||||
|
objs2.setAttribute('type', 'hidden');
|
||||||
|
objs2.setAttribute('name', 'id');
|
||||||
|
objs2.setAttribute('value', `${user.id}`);
|
||||||
|
formauth.appendChild(objs2);
|
||||||
|
|
||||||
|
let objs3 = document.createElement('input');
|
||||||
|
objs3.setAttribute('type', 'hidden');
|
||||||
|
objs3.setAttribute('name', 'code');
|
||||||
|
objs3.setAttribute('value', `${user.token}`);
|
||||||
|
formauth.appendChild(objs3);
|
||||||
|
|
||||||
|
let objs4 = document.createElement('input');
|
||||||
|
objs4.setAttribute('type', 'hidden');
|
||||||
|
objs4.setAttribute('name', 'nickname');
|
||||||
|
objs4.setAttribute('value', `${user.nickname}`);
|
||||||
|
formauth.appendChild(objs4);
|
||||||
|
|
||||||
|
let objs5 = document.createElement('input');
|
||||||
|
objs5.setAttribute('type', 'hidden');
|
||||||
|
objs5.setAttribute('name', 'provider');
|
||||||
|
objs5.setAttribute('value', `${user.provider}`);
|
||||||
|
formauth.appendChild(objs5);
|
||||||
|
|
||||||
|
let objs6 = document.createElement('input');
|
||||||
|
objs6.setAttribute('type', 'hidden');
|
||||||
|
objs6.setAttribute('name', 'providerId');
|
||||||
|
objs6.setAttribute('value', `${user.providerId}`);
|
||||||
|
formauth.appendChild(objs6);
|
||||||
|
|
||||||
|
let objs7 = document.createElement('input');
|
||||||
|
objs7.setAttribute('type', 'hidden');
|
||||||
|
objs7.setAttribute('name', 'verify');
|
||||||
|
objs7.setAttribute('value', `${user.verify}`);
|
||||||
|
formauth.appendChild(objs7);
|
||||||
|
|
||||||
|
let objs8 = document.createElement('input');
|
||||||
|
objs8.setAttribute('type', 'hidden');
|
||||||
|
objs8.setAttribute('name', 'agree');
|
||||||
|
objs8.setAttribute('value', `${user.agree}`);
|
||||||
|
formauth.appendChild(objs8);
|
||||||
|
|
||||||
|
formauth.setAttribute('method', 'post');
|
||||||
|
formauth.setAttribute('action', '/authorize/gamepot');
|
||||||
|
document.body.appendChild(formauth);
|
||||||
|
formauth.submit();
|
||||||
|
}
|
||||||
|
|
||||||
|
function onLoginFail(error) {
|
||||||
|
const formauth = document.createElement('form');
|
||||||
|
formauth.setAttribute('method', 'post');
|
||||||
|
formauth.setAttribute('action', 'actionsquare://error?errormsg='+ encodeURIComponent(`${error.code}-${error.message}`));
|
||||||
|
document.body.appendChild(formauth);
|
||||||
|
formauth.submit();
|
||||||
|
}
|
||||||
|
|
||||||
|
function onSignInGoogle(error, user) {
|
||||||
|
if (error) {
|
||||||
|
// 로그인 실패
|
||||||
|
onLoginFail(error);
|
||||||
|
} else {
|
||||||
|
onLoginsuccess(user);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function SignIn(channeltype) {
|
||||||
|
GP.login(channeltype, function (user, error) {
|
||||||
|
if (error) {
|
||||||
|
// 로그아웃 실패
|
||||||
|
onLoginFail(error);
|
||||||
|
} else {
|
||||||
|
// 로그인 성공
|
||||||
|
onLoginsuccess(user)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function Logout() {
|
||||||
|
// GP.logout(function (error) {});
|
||||||
|
GP.logout(function (result, error) {
|
||||||
|
if (error) {
|
||||||
|
// 로그아웃 실패
|
||||||
|
alert(error); // 오류 메세지
|
||||||
|
} else {
|
||||||
|
// 로그아웃 아웃 완료
|
||||||
|
alert("로그아웃 아웃 완료");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function deleteMember() {
|
||||||
|
if (!userId) {
|
||||||
|
alert("로그인을 먼저 해 주세요.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
GP.deleteMember(userId, function (result, error) {
|
||||||
|
if (error) {
|
||||||
|
// 회원탈퇴 실패
|
||||||
|
alert(error); // 오류 메세지
|
||||||
|
} else {
|
||||||
|
// 회원탈퇴 아웃 완료
|
||||||
|
alert("회원탈퇴 성공!");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
window.onload = function () {
|
||||||
|
// 프로젝트 ID는 게임팟 대시보드에서 확인할 수 있습니다.
|
||||||
|
var project_id = "dbfe1334-6dde-43e0-b8a9-cc0733d4c60e";
|
||||||
|
|
||||||
|
var gamepotConfig = {
|
||||||
|
|
||||||
|
api_url: "https://gpapps.gamepot.ntruss.com",
|
||||||
|
google_signin_client_id:
|
||||||
|
"46698421246-aeg0c2pmsgifr3fi06jgnqag5u8ph3kn.apps.googleusercontent.com",
|
||||||
|
google: {
|
||||||
|
callback: onSignInGoogle, // callback 버튼
|
||||||
|
renderButton: "googleRenderButton", // 버튼 DIV 이름
|
||||||
|
option: {
|
||||||
|
// google button option
|
||||||
|
size: "large",
|
||||||
|
theme: "outline",
|
||||||
|
width: "375",
|
||||||
|
text: "signup_with",
|
||||||
|
shape: "rectangular",
|
||||||
|
logo_alignment: "left",
|
||||||
|
locale: "ko_kr"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
facebook_app_id: "2930531180541185",
|
||||||
|
apple_client_id: "auth.service.action2quare.com",
|
||||||
|
apple_redirect_uri: "{{.RedirectBaseUrl}}/authorize/apple",
|
||||||
|
api_key: "b94615af2a956facd2add44ea50529154b35f520de85673d",
|
||||||
|
};
|
||||||
|
|
||||||
|
GP.initialize(project_id, gamepotConfig);
|
||||||
|
};
|
||||||
|
|
||||||
|
</script>
|
||||||
|
<div align="center">
|
||||||
|
|
||||||
|
|
||||||
|
<br />
|
||||||
|
<div id="googleRenderButton"></div>
|
||||||
|
<br />
|
||||||
|
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<table class="table" border="0" width="400" bgcolor="white" style="table-layout: fixed" onclick="SignIn(GP.ChannelType.APPLE)">
|
||||||
|
<tr>
|
||||||
|
<td align="center">
|
||||||
|
<div
|
||||||
|
id="appleid-signin"
|
||||||
|
data-mode="center-align"
|
||||||
|
data-type="sign-in"
|
||||||
|
data-color="white"
|
||||||
|
data-border="true"
|
||||||
|
data-border-radius="15"
|
||||||
|
data-width="375"
|
||||||
|
data-height="40"
|
||||||
|
data-logo-size="medium"
|
||||||
|
data-logo-position="47"
|
||||||
|
data-label-position="135"
|
||||||
|
style="pointer-events: none"
|
||||||
|
></div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<br />
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
BIN
www/toCDN/apple-id-sign-up-with_2x.png
Normal file
BIN
www/toCDN/apple-id-sign-up-with_2x.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 8.4 KiB |
276
www/toCDN/gamepot-sdk-javascript-1.0.19-b1.min.js
vendored
Normal file
276
www/toCDN/gamepot-sdk-javascript-1.0.19-b1.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user