maingate 이전

This commit is contained in:
2023-05-24 12:16:03 +09:00
commit 1fdfe3f45b
48 changed files with 5677 additions and 0 deletions

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
.vscode/
__debug_bin.exe
*.log
*.exe

64
config.json Normal file
View 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
View File

@ -0,0 +1 @@
{}

View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

1
console/index.html Normal file
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

BIN
console/logo512.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

25
console/manifest.json Normal file
View 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
View File

@ -0,0 +1,3 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:

View 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}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
/*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/facebook/regenerator/blob/main/LICENSE */

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View 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"}}]);

View 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.
*/

File diff suppressed because one or more lines are too long

View 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"})]})]})})})})}}}]);

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
/*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/facebook/regenerator/blob/main/LICENSE */

File diff suppressed because one or more lines are too long

View 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.
*/

File diff suppressed because one or more lines are too long

View 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
View 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
View 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
View 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)
}

View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

File diff suppressed because one or more lines are too long