Seize the day

SEARCH RESAULT : 글 검색 결과 - 전체 글 (총 493개)

POST : Android Dev Study

cpp QRCode Login + Android QrCode scanning

CPP에서 Qrcode 생성하기

nayuki/QR-Code-generator: High-quality QR Code generator library in Java, TypeScript/JavaScript, Python, C++, C, Rust. (github.com) 간단히 파일 두 개로 구현된 라이브러리 이용하기 

 

GitHub - nayuki/QR-Code-generator: High-quality QR Code generator library in Java, TypeScript/JavaScript, Python, C++, C, Rust.

High-quality QR Code generator library in Java, TypeScript/JavaScript, Python, C++, C, Rust. - GitHub - nayuki/QR-Code-generator: High-quality QR Code generator library in Java, TypeScript/JavaScri...

github.com

 

오랜만에 MFC 하려니 힘들구나.. 기억이 안나..

void CDlgQrcode::OnPaint() {
	CPaintDC dc(this);

	if (url_.IsEmpty()) {
		return;
	}

	// Manual operation
	CAtlStringA url = dfx::toUtf8(url_.GetBuffer(0));
	QrCode qr1 = QrCode::encodeText(url.GetBuffer(0), QrCode::Ecc::LOW);

	CBrush brushWhite, brushBlack;
	brushWhite.CreateSolidBrush(RGB(255, 255, 255));
	brushBlack.CreateSolidBrush(RGB(0, 0, 0));
	
	const int cellSize = 10;

	for (int y = 0; y < qr1.getSize(); y++) {
		for (int x = 0; x < qr1.getSize(); x++) {
			int start = (x+1) * cellSize; 
			int end = (y+1) * cellSize;
			CRect rc(start, end, start + cellSize, end + cellSize);
			if (qr1.getModule(x, y)) {
				dc.FillRect(&rc, &brushBlack);
			}
			else {
				dc.FillRect(&rc, &brushWhite);
			}
		}
	}
}

 

deno/ oak 서버에서  long polling하면서, Android에서 Qrcode 스캔하여 응답하기를 30초간 기다리기

router
    .get(v1 + "qrcode-login/:code", qrcodeLoginGetMethod)
    .post(v1 + "qrcode-login/:code", qrcodeLoginPostMethod)
    .post(v1 + "qrcode-accept/:code", qrcodeAccept)
    
const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
const requestMap = new Map<string, any>()

// Chrome으로 열 경우, 안내 페이지를 연다. 안드로이드 앱의 SchemeActivity를 여는 lt:// link를 제공한다.
export const qrcodeLoginGetMethod = (context: any) => {
    const response = context.response
    const code: string = context.params.code
    if (!isValidQrcode(code)) {
        response.body = "<html><body><h1>Invalid qrcode login</h1></body></html>"
    } else {
        response.body = `<html>
    <body>
        <h1>Qrcode login</h1>
        <br> 
        <button onClick="location.href='lt://qrcode-login/${code}';"><h3>Open app(If you have app)</h3></button></p>
        <button onClick="location.href='https://play.google.com/store/apps/details?id=com.mdiwebma.screenshot';"><h3>Install app</h3></button>
    </body>
</html>`
    }
    response.status = 200    
}


export const qrcodeLoginPostMethod = async (context: any) => {
    const request = context.request
    const ip = request.ip
    console.log('qrcodeLogin ip='+ip)

    const response = context.response        
    const code: string = context.params.code
    
    if (!isValidQrcode(code)) throw new ErrorCode("Invalid Qrcode login", kErrorQrcode)
    
    await qrcodeLoginByLongPolling(code, ip, response)
}

async function qrcodeLoginByLongPolling(code: string, ip: string, response: any) {
    try {
        requestMap.set(code, {ip: ip})

        for(let i = 0; i< 10; i++) {
            await sleep(3000)
            //console.log(`${guid} ${i}`)
            
            const value = requestMap.get(code)
            const encrypted = value.encrypted
            if (typeof encrypted == "boolean") {
                handleResult(response, value)        
                return
            }
        }

        throw new ErrorCode("Qrcode login timeout", kErrorQrcode)
    } catch(err) {
        console.log(err)
        throw new ErrorCode(err.toString(), kErrorQrcode)
    } finally {
        requestMap.delete(code)
    }
}

isValidQrcode() : qrcode는 hash하여, 간단히 자체적으로 validation을 체크한다. 

qrcodeLoginGetMethod() : 안드로이드앱이 아닌, 일반 브라우저에서 Scanning한 경우라면 qrcodeLoginGetMethod 가 호출된다. 적당히 안내를 해서 앱으로 연결시키던지, 앱 다운로드 페이지로 안내한다. 

qrcodeLoginPostMethod() : Cpp 클라이언트에서 연결을 하고 있다가,  안드로이드앱에서 qrcode-accept/{code} 를 호출하여, requestMap에 값을 입력하면 그 값을 cpp 클라이언트에 응답한다. 

 

안드로이드 Qrcode 스캐닝

https://github.com/dm77/barcodescanner 의 라이브러리를 이용한다.

build.gradle

implementation 'me.dm7.barcodescanner:zxing:1.9.13'

 

AndroidManifest.xml

<uses-permission android:name="android.permission.CAMERA" />

<activity
            android:name=".activity.ScannerActivity"
            android:configChanges="orientation|screenSize|keyboardHidden"
            android:screenOrientation="portrait" />
            
<activity
            android:name=".activity.SchemeActivity"
            android:launchMode="singleTask"
            android:theme="@style/AppTheme.Transparent">
            <intent-filter>
                <action android:name="android.intent.action.VIEW" />

                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="android.intent.category.BROWSABLE" />

                <data android:scheme="lt" />
            </intent-filter>
            <!--            App link guide -->
            <!--            https://developer.android.com/studio/write/app-link-indexing?hl=ko-->
            <!--            https://developer.android.com/guide/topics/manifest/data-element.html-->
            <!--            <intent-filter>-->
            <!--                <action android:name="android.intent.action.VIEW" />-->

            <!--                <category android:name="android.intent.category.DEFAULT" />-->
            <!--                <category android:name="android.intent.category.BROWSABLE" />-->
            <!--                -->
            <!--                <data-->
            <!--                    android:scheme="https"-->
            <!--                    android:host="api.blabla.com"-->
            <!--                    android:port="443"-->
            <!--                    android:pathPrefix="/lt/v1/qrcode-login/" />-->
            <!--            </intent-filter>-->
</activity>

App link는 OS가 자동적으로 특정 url을 app으로 리다이렉트 시켜주는 것 같다.  다만 서버에 디지털 에셋 링크 파일을 만들어서 업로드해 두어야한다.  https://developer.android.com/studio/write/app-link-indexing?hl=ko   참고

 

ScannerActivity

import android.Manifest
import android.os.Bundle
import com.google.zxing.Result
import com.mdiwebma.base.BaseAppCompatActivity
import com.mdiwebma.base.utils.ToastUtils
import me.dm7.barcodescanner.zxing.ZXingScannerView

class ScannerActivity : BaseAppCompatActivity(0), ZXingScannerView.ResultHandler {

    private var scannerView: ZXingScannerView? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        scannerView = ZXingScannerView(this)
        setContentView(scannerView)
        
        checkPermissions(arrayOf(Manifest.permission.CAMERA)) { isAllGranted, _ ->
            if (isAllGranted) {
                scannerView?.setResultHandler(this)
                scannerView?.startCamera()
            } else {
                finish()
            }
        }
    }

    override fun onResume() {
        super.onResume()
        // TODO resumeCamera 등 처리필요        
    }

    override fun onPause() {
        super.onPause()
        scannerView?.stopCamera()
    }

    override fun handleResult(rawResult: Result) {
        if ("/qrcode-login/" in rawResult.text) {
            val nodes = rawResult.text.split("/")
            val code = nodes.last()
            if (code.length != 36) {
                scannerView?.resumeCameraPreview(this)
                ToastUtils.show("hmm??..$code")
                return
            }

            lifecycleScope.launch {
                try {
                    withContext(Dispatchers.IO) {
                        SyncManager().qrcodeAccept(code)
                    }
                } catch (ex: Exception) {
                    ToastUtils.show(ex.toString())
                }
                finish()
            }
        } else {
            // retry
            ToastUtils.show(rawResult.text)
            scannerView?.resumeCameraPreview(this)
        }
    }
}

 

모바일 크롬에서 lt:// 링크를 클릭했을때 앱으로 열리게..
SchemeActivity.kt

class SchemeActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val deepLinkUri = intent.data ?: run {
            finish()
            return
        }
        val url = deepLinkUri.toString()
        if ("/qrcode-login/" in url) {
            // TODO: code 추출
            // jwt 를 만들어서,
            // qrcode-accept/{code} 로 전달..
        }
        finish()
    }
}

 

top

posted at

2021. 8. 29. 00:31


POST : Backend study

Deno oak 서버에서 JWT 인증하기

error_code.ts

export class ErrorCode extends Error {
    code: number
    
    constructor(message: string, code: number) {
        super(message)
        this.code = code
    }
}

export const kCodeMaintenance: number = 1
export const kCodeJwtAuth: number = 2

export function throwJwtError(message: string) {
    throw new ErrorCode("jwt auth error: " + message, kCodeJwtAuth)
}

verify_token.ts

import {encode as base64url_encode, decode as base64url_decode} from "https://deno.land/std@0.103.0/encoding/base64url.ts"
import {hmac as createHmac} from "https://deno.land/x/crypto@v0.10.0/hmac.ts"
import {throwJwtError} from "./error_code.ts"

const PRIVATE_SECRET: Uint8Array = new TextEncoder().encode("private secret")

/*
 *  JWT token 유효성을 검사하고, 데이타를 추출하여 리턴한다.
 *  예외가 발생하면 ErrorCode를 던진다.
 *  const [uid, muid, did] = verifyJwtToken(token)
 */
export function verifyJwtToken(token: string): [string, string, string] {
    if (token == null) {
        throwJwtError("empty token")
    }
    const [header, payload, signiture] = token.split('.')
    const value = new TextEncoder().encode(header + "." + payload)
    const hmacValue = createHmac('sha256', PRIVATE_SECRET, value)
    const signiture2 = base64url_encode(hmacValue)
    if (signiture !== signiture2) {
        throwJwtError("invalid signiture")
    }
    const payloadJson: string = new TextDecoder().decode(base64url_decode(payload))
    const payloadObj = JSON.parse(payloadJson)
    const exp = payloadObj.exp || null
    if (typeof exp != "number") {
        throwJwtError("invalid exp")
    }
            
    if (exp <= Math.floor(Date.now()/1000)) {
        throwJwtError("expired token")
    }
    
    const uid = payloadObj.uid || null
    if (uid == null) {
        throwJwtError("empty uid")
    }
        
    const muid = payloadObj.muid || ""
    const did = payloadObj.did || ""
    return [uid, muid, did]
}

// request: oak request
export function verifyJwtRequest(request: any): [string, string, string] {
    return verifyJwtToken(request.headers.get("json-web-token"))
}

routes.ts

import { Router } from "https://deno.land/x/oak/mod.ts"
import getHello from "./v1/hello.ts"
import getUser from "./v1/user.ts"

const router = new Router()

router
    .get("/lt/v1/hello", getHello)
    .post("/lt/v1/user", getUser)

export default router

server.ts

import { Application, Context } from "https://deno.land/x/oak/mod.ts"
import router from './routes.ts'

const errorHandler = async (ctx: Context, next: any) => {
    try {
      await next();
    } catch (err) {
      ctx.response.status = 200;
      ctx.response.body = { code: err.code || 500, message: err.message }
    }
}

const _404 = async (ctx: Context) => {
    ctx.response.status = 200;
    ctx.response.body = { code: 404, message: "404 Not Found!" }
}


const app = new Application()
const port = 8000
app.use(errorHandler);
app.use(router.routes())
app.use(router.allowedMethods())
app.use(_404)

console.log("running:  api server port: " + port);
await app.listen({ port: port });

 

v1/hello.ts

import { verifyJwtRequest } from "../verify_token.ts"

const getHello = async ({ request, response }: { request: any; response: any }) => {
    const [uid, muid, did] = verifyJwtRequest(request)
    
    console.log(uid + ":" + muid + ":" + did)

    //const body = await request.body()
    //const text: string = body.value
    response.body = { 
        message: 'Hello OK',
        uid: uid,
        muid: muid,
        did: did
    }
    response.status = 200
}

export default getHello
top

posted at

2021. 8. 10. 22:08


POST : Backend study

Mongo db의 Array정보의 find,update,delete 실습

# 하나 추가

> db.user.insertOne({uid: "uid1", name: "name1", email: "email1"})
> db.user.find({}, {_id: 0})
{ "uid" : "uid1", "name" : "name1", "email" : "email1" }

# db 배열에 아이템 하나 추가하기

> db.user.updateOne({uid: "uid1"}, { $push: { db: {did: "did1", name: "db1"} } })
{ "acknowledged" : true, "matchedCount" : 1, "modifiedCount" : 1 }
> db.user.find({}, {_id: 0})
{ "uid" : "uid1", "name" : "name1", "email" : "email1", "db" : [ { "did" : "did1", "name" : "db1" } ] }

# db 배열에 아이템  여러개 추가하기

> db.user.updateOne(
  {uid: "uid1"}, 
  { $push: 
    { 
      db: { 
        $each: [ 
          {did: "did2", name: "db2"},
          {did: "did3", name: "db3"}
        ] 
      }
    }
  })
  
  > db.user.find({}, {_id: 0})
{ "uid" : "uid1", "name" : "name1", "email" : "email1", "db" : [ { "did" : "did1", "name" : "db1" }, { "did" : "did2", "name" : "db2" }, { "did" : "did3", "name" : "db3" } ] }

# db안에 db3인 요소만 제거

> db.user.updateOne(
   {uid: "uid1"},
   { $pull: {
      db: { did: "did3" }
     }
   })
{ "acknowledged" : true, "matchedCount" : 1, "modifiedCount" : 1 }
> db.user.find({}, {_id: 0})
{ "uid" : "uid1", "name" : "name1", "email" : "email1", "db" : [ { "did" : "did1", "name" : "db1" }, { "did" : "did2", "name" : "db2" } ] }

# db안에 did2인 요소의 name을 수정하기
Update Arrays in a Document — Node.js (mongodb.com)

> db.user.updateOne(
  {uid: "uid1", "db.did": "did2"},
  { $set: { "db.$.name": "name22" } }
  )
{ "acknowledged" : true, "matchedCount" : 1, "modifiedCount" : 1 }

> db.user.find({}, {_id: 0})
{ "uid" : "uid1", "name" : "name1", "email" : "email1", "db" : [ { "did" : "did1", "name" : "db1" }, { "did" : "did2", "name" : "name22" } ] }
>

# array에 매치된 document 찾기

> db.user.find({
  "db.did": "did1"
})


{ "_id" : ObjectId("611361f9bef2364fbcaf7d3f"), "uid" : "uid1", "name" : "name1", "email" : "email1", "db" : [ { "did" : "did1", "name" : "db1" }, { "did" : "did2", "name" : "name22" } ] }
top

posted at

2021. 8. 10. 22:06


POST : Backend study

안드로이드 앱에서 Firebase의 JWT token 가져오기 실습

https://dajkim76.tistory.com/533 에서 이어서 ...

Firebase에 프로젝트 추가하기

1. firebase에 안드로이드 앱추가 : 

    sha1 hash 추가 : Authenticating Your Client  |  Google Play services  |  Google Developers  

./gradlew signingReport

2. google-services.json 다운로드하여, app 폴더에 복사
   gradle 설정 추가 :   Android 프로젝트에 Firebase 추가 (google.com)

3. gradle에 auth 라이브러리 추가 : Firebase 인증 (google.com)

4. 각종. 로그인 처리 : FirebaseUI로 손쉽게 Android 앱에 로그인 추가 (google.com)

 

구글 로그인 테스트

snippets-android/auth at master · firebase/snippets-android (github.com) 참고

인증 체계에서 지원하는 인증 방식에 email 과 구글 로그인을 체크하고, 
Firebase 설정에 지원 email을 지정한다. 
OAuth2 web_client_id 는 인증탭에서 구하기 (R.string.default_web_client_id를 사용해도 문제는 없는 것 같다 )

    private val googleSignInClient: GoogleSignInClient by lazy {
        val googleSignInOptions = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
            .requestIdToken(getString(R.string.web_client_id))
            .requestEmail()
            .build()
        GoogleSignIn.getClient(this, googleSignInOptions)
    }

    @OnMenuClick(R.id.menu_google_login)
    fun onClickGoogleLogin(menuItem: MenuItem) {
        FirebaseAuth.getInstance().currentUser?.let {
            ToastUtils.show("이미 로그인됨: " + it.uid)
            return
        }
        startActivityForResult(googleSignInClient.signInIntent, 1)
    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        if (requestCode == 1) {
            val task = GoogleSignIn.getSignedInAccountFromIntent(data)
            try {
                // Google Sign In was successful, authenticate with Firebase
                val account = task.getResult(ApiException::class.java)!!
                Log.d(TAG, "firebaseAuthWithGoogle:" + account.id)
                firebaseAuthWithGoogle(account.idToken!!)
            } catch (e: ApiException) {
                // Google Sign In failed, update UI appropriately
                ToastUtils.show("Google sign in failed" + e.toString())
            }
        }
    }

    private fun firebaseAuthWithGoogle(idToken: String) {
        val credential = GoogleAuthProvider.getCredential(idToken, null)
        FirebaseAuth.getInstance().signInWithCredential(credential)
            .addOnCompleteListener(this) { task ->
                if (task.isSuccessful) {
                    // Sign in success, update UI with the signed-in user's information

                    val user = FirebaseAuth.getInstance().currentUser
                    ToastUtils.show("signInWithCredential:success = " + user?.uid)
                    //updateUI(user)
                } else {
                    // If sign in fails, display a message to the user.
                    ToastUtils.show("signInWithCredential:failure" +  task.exception.toString())
                    //updateUI(null)
                }
            }
    }

 

Firbase functions 호출하기 (Firbasebse 로그인되어 있지 않으면 실패를 응답하므로 먼저 로그인)

앱에서 함수 호출  |  Firebase (google.com)  참고

    @OnMenuClick(R.id.menu_create_jwt)
    fun onClickCreateJwt(menuItem: MenuItem) {
        val text: String = JSONObject()
            .put("expireSeconds", 60 * 60 * 24)
            .put("did", "my_did")
            .put("muid", "my_muid")
            .toString()

        val data = hashMapOf(
            "text" to text
        )

        FirebaseFunctions.getInstance()
            .getHttpsCallable("createJwtToken")
            .call(data)
            .addOnCompleteListener { task ->
                if (task.isSuccessful) {
                    val dataString = task.result?.data?.toString()
                    val dataMap = task.result?.data as? Map<String, Object>;
                    if (dataMap != null) {
                        if ((dataMap["code"] as? Int) == 0) {
                            val resultMap = dataMap["result"] as? Map<String, Object>
                            val token = resultMap?.get("token") as? String
                            val exp = resultMap?.get("exp") as? Int
                            Log.d("__T", "token= $token\nexp=$exp")
                            DUtils.alert("token= $token\nexp=$exp")
                        }
                    }
                } else {
                    val e = task.exception
                    if (e is FirebaseFunctionsException) {
                        val code = e.code
                        val details = e.details
                    }
                    DUtils.alert(e.toString())
                }
            }
    }

 

TODO:  App check라는게 있어서 호출하는 앱이 유효한지 한 번 더 검증해주는 것 같다. 
Android에서 SafetyNet으로 앱 확인 활성화  |  Firebase (google.com) 

top

posted at

2021. 8. 9. 16:30


POST : Backend study

GCP 우분투 focal에 Nginx/Mongodb/Deno 다시 설치하기

https://dajkim76.tistory.com/532 의 과정을 다시한다. 

MongoDB 5.0.x 설치

Mongodb 5.0이 우분투 하위버전에서 설치가 되지 않아서 GCP에 디스크를 추가하고 상위버전 우분투 이미지를 사용했다.
Mongodb 5.0.2 설치는 간단히 완료됬다. 
Install MongoDB Community Edition on Ubuntu — MongoDB Manual

kimdaejeong@base-backend-1:~$ history
    1  wget -qO - https://www.mongodb.org/static/pgp/server-5.0.asc | sudo apt-key add -
    2  echo "deb [ arch=amd64,arm64 ] https://repo.mongodb.org/apt/ubuntu focal/mongodb-org/5.0 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-5.0.list
    3  sudo apt-get update
    4  sudo apt-get install -y mongodb-org
    5  ps --no-headers -o comm 1
    6  sudo systemctl start mongod
    7  mongo

 

Nginx 설치하기 

    9  sudo apt-get install nginx
   10  nginx -V
   11  sudo service nginx start

nginx version: nginx/1.18.0 (Ubuntu) 

Nginx에 SSL 적용하기

   20  sudo apt-get install certbot
   21  cat /etc/nginx/sites-enabled/default
   22  sudo certbot certonly --webroot -w /var/www/html -d app.mdiwebma.com
   23  sudo ls -al /etc/letsencrypt/live/app.mdiwebma.com
   24  sudo vi  /etc/nginx/sites-enabled/default
   25  sudo service nginx restart

Deno 설치

   14  sudo apt-get install unzip -y
   15  curl -fsSL https://deno.land/x/install/install.sh | sh
   16  vi .bashrc
   17  . .bashrc
   18  deno

 

서버 동작 테스트

   28  mkdir server
   29  cd server
   30  vi server.ts
   31  deno run --inspect  --allow-net --unstable server.ts &

https://app.mdiwebma.com/lt/v1/tslist 로 연결 확인 

이 모든 과정을 삽질없이 완료했다. 이전 영구 디스크 이미지는 이제 지워도 될 것 같다. 

top

posted at

2021. 8. 5. 22:10


POST : Backend study

Firebase firestore의 보안 규칙 강화

예전에 공부했둔 것 꺼내서 정리했다. Firestore는 Nosql 이라서 대부분의 db코드가 클라에 있고, 그러다 보니 보안을 클라에서만 의존하기에는 불안해서, 서버쪽에서 강화하는쪽으로 고민했었다. 

real-groups/{groupId} 는 멤버만 조회할 수 있어야 했다. 
real-groups/{groupId}/members/{userId} 의 멤버의 가입은 초대 코드를 알아야만 가입할 수 있다. 
이 두 가지 조건을 보안 규칙에 적용해서, 클라이언트에서 잘 못된 호출이나 변조된 호출이 있더라도 데이타를 보호할 수 있도록 했다. 

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {    

    // real
    match /real-users/{userId} {
      allow create, read, update, delete: if request.auth.uid == userId;
    }    
    match /real-users/{userId}/groups/{groupId} {
      allow create, read, update, delete: if request.auth.uid == userId;
    }
    match /real-open-groups/{groupId} {      
      // 로그인된 사용자는 생성, 읽기 가능
      allow create, read: if request.auth.uid != null;
      // 삭제는 document를 만든사람만 가능
      allow delete: if get(/databases/$(database)/documents/real-groups/$(groupId)).data.owner == request.auth.uid;
      // 업데이트는 document를 만든사람이거나, 이 그룹에 참여한 사람만 가능
      allow update: if get(/databases/$(database)/documents/real-groups/$(groupId)).data.owner == request.auth.uid ||
        exists(/databases/$(database)/documents/real-groups/$(groupId)/members/$(request.auth.uid));
    }
    match /real-groups/{groupId} {      
      allow create: if request.auth.uid != null;
      allow delete: if resource.data.owner == request.auth.uid;
      allow read, update: if resource.data.owner == request.auth.uid ||
        exists(/databases/$(database)/documents/real-groups/$(groupId)/members/$(request.auth.uid));      
    }
    match /real-groups/{groupId}/members/{userId} {
      // 멤버추가는 그룹의 관리자이거나, 가입코드를 알고 있는 경우에만 가능
      allow create: if get(/databases/$(database)/documents/real-groups/$(groupId)).data.owner == request.auth.uid ||
        get(/databases/$(database)/documents/real-groups/$(groupId)).data.code == request.resource.data.code;
      // 멤버만 읽기 업데이트 가능
      allow read, update: if exists(/databases/$(database)/documents/real-groups/$(groupId)/members/$(request.auth.uid));
      // 멤버 삭제는 그룹멤버이면서, 잔고가 0인 경우만 가능
      allow delete: if exists(/databases/$(database)/documents/real-groups/$(groupId)/members/$(request.auth.uid)) &&
      	get(/databases/$(database)/documents/real-groups/$(groupId)/members/$(userId)).data.money == 0;
    }
    match /real-groups/{groupId}/restaurants/{restId} {
      // 식당추가는 그룹 멤버만 가능
      allow create, read, update, delete: if exists(/databases/$(database)/documents/real-groups/$(groupId)/members/$(request.auth.uid));
    }
  }
}
top

posted at

2021. 8. 5. 11:23


CONTENTS

Seize the day
BLOG main image
김대정의 앱 개발 노트와 사는 이야기
RSS 2.0Tattertools
공지
아카이브
최근 글 최근 댓글
카테고리 태그 구름사이트 링크