Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: permissions revamp v2 #273

Merged
merged 37 commits into from
Jul 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
baba8db
feat: revamp permissions component
im-adithya Jul 3, 2024
4976123
chore: changes
im-adithya Jul 4, 2024
e7a8a20
chore: changes
im-adithya Jul 4, 2024
c070d22
chore: add view mode for show app screen
im-adithya Jul 8, 2024
ee2ab64
chore: further changes
im-adithya Jul 9, 2024
21b0d02
feat: new illustration for linking account (#254)
reneaaron Jul 11, 2024
abf4027
fix: icon props (#256)
reneaaron Jul 11, 2024
c82f53e
Merge branch 'master' into task-permissions-revamp
im-adithya Jul 11, 2024
f7caaae
fix: use date from frontend
im-adithya Jul 11, 2024
d4974cf
chore: add expiryselect component
im-adithya Jul 11, 2024
70183e4
chore: further changes
im-adithya Jul 11, 2024
2c73c47
chore: further changes
im-adithya Jul 11, 2024
8191a62
chore: add date-fns for date picker
im-adithya Jul 11, 2024
0aebbac
chore: further changes
im-adithya Jul 11, 2024
03b0518
chore: further changes
im-adithya Jul 11, 2024
4ed84cb
fix: add central LDK gossip node to help gossip new public channels (…
rolznz Jul 11, 2024
e37e5cf
fix: make dialog responsive (#258)
reneaaron Jul 12, 2024
2310386
chore: further changes
im-adithya Jul 12, 2024
428c3bd
typo
bumi Jul 12, 2024
54a38b0
chore: spacing issues
im-adithya Jul 12, 2024
6c43f90
chore: use scopes from capabilities
im-adithya Jul 12, 2024
350c5ea
typo (#264)
im-adithya Jul 12, 2024
2569a04
chore: change scope type descriptions
im-adithya Jul 12, 2024
1af3283
chore: styling fixes
im-adithya Jul 12, 2024
7a07006
chore: fix typings
im-adithya Jul 12, 2024
21bcd22
chore: budget renewal component
im-adithya Jul 12, 2024
7a3a8a2
fix: LDK mark channel as inactive and show error if counterparty forw…
rolznz Jul 13, 2024
1380934
chore: remove unnecessary dark classes (#255)
im-adithya Jul 13, 2024
9de731d
fix: links to open first channel in sidebar and onboarding checklist …
rolznz Jul 13, 2024
4a4d8d9
feat: improve migrate node UI (#269)
rolznz Jul 13, 2024
831d7bf
fix: migrate node copy (#270)
rolznz Jul 13, 2024
7b9e4d5
fix: stop nostr when app is shutdown and use context to stop lnclient…
rolznz Jul 13, 2024
42242bd
Merge remote-tracking branch 'origin/master' into task-permissions-re…
rolznz Jul 14, 2024
979d9d4
fix: permissions revamp WIP
rolznz Jul 14, 2024
7346641
Merge branch 'feat/dynamic-budgets' into feat/permissions-revamp-v2
rolznz Jul 14, 2024
0aa4a9c
feat: basic isolated apps UI
rolznz Jul 14, 2024
9bea336
fix: new app connection, edit app connection, deep linking
rolznz Jul 14, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,7 @@ If the client creates the secret the client only needs to share the public key o
- `budget_renewal` (optional) reset the budget at the end of the given budget renewal. Can be `never` (default), `daily`, `weekly`, `monthly`, `yearly`
- `request_methods` (optional) url encoded, space separated list of request types that you need permission for: `pay_invoice` (default), `get_balance` (see NIP47). For example: `..&request_methods=pay_invoice%20get_balance`
- `notification_types` (optional) url encoded, space separated list of notification types that you need permission for: For example: `..&notification_types=payment_received%20payment_sent`
- `isolated` (optional) makes an isolated app connection with its own balance and only access to its own transaction list. e.g. `&isolated=true`. If using this option, you should not pass any custom request methods or notification types, nor set a budget or expiry.

Example:

Expand Down
1 change: 1 addition & 0 deletions alby/alby_oauth_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,7 @@ func (svc *albyOAuthService) LinkAccount(ctx context.Context, lnClient lnclient.
renewal,
nil,
scopes,
false,
)

if err != nil {
Expand Down
30 changes: 21 additions & 9 deletions api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,14 @@ func (api *api) CreateApp(createAppRequest *CreateAppRequest) (*CreateAppRespons
}
}

app, pairingSecretKey, err := api.dbSvc.CreateApp(createAppRequest.Name, createAppRequest.Pubkey, createAppRequest.MaxAmountSat, createAppRequest.BudgetRenewal, expiresAt, createAppRequest.Scopes)
app, pairingSecretKey, err := api.dbSvc.CreateApp(
createAppRequest.Name,
createAppRequest.Pubkey,
createAppRequest.MaxAmountSat,
createAppRequest.BudgetRenewal,
expiresAt,
createAppRequest.Scopes,
createAppRequest.Isolated)

if err != nil {
return nil, err
Expand Down Expand Up @@ -212,6 +219,11 @@ func (api *api) GetApp(dbApp *db.App) *App {
Scopes: requestMethods,
BudgetUsage: budgetUsage,
BudgetRenewal: paySpecificPermission.BudgetRenewal,
Isolated: dbApp.Isolated,
}

if dbApp.Isolated {
response.Balance = queries.GetIsolatedBalance(api.db, dbApp.ID)
}

if lastEventResult.RowsAffected > 0 {
Expand Down Expand Up @@ -244,6 +256,11 @@ func (api *api) ListApps() ([]App, error) {
CreatedAt: dbApp.CreatedAt,
UpdatedAt: dbApp.UpdatedAt,
NostrPubkey: dbApp.NostrPubkey,
Isolated: dbApp.Isolated,
}

if dbApp.Isolated {
apiApp.Balance = queries.GetIsolatedBalance(api.db, dbApp.ID)
}

for _, appPermission := range permissionsMap[dbApp.ID] {
Expand Down Expand Up @@ -317,15 +334,11 @@ func (api *api) Stop() error {
return errors.New("LNClient not started")
}

// TODO: this should stop everything related to the lnclient
// stop the lnclient
// stop the lnclient, nostr relay etc.
// The user will be forced to re-enter their unlock password to restart the node
err := api.svc.StopLNClient()
if err != nil {
logger.Logger.WithError(err).Error("Failed to stop LNClient")
}
api.svc.StopApp()

return err
return nil
}

func (api *api) GetNodeConnectionInfo(ctx context.Context) (*lnclient.NodeConnectionInfo, error) {
Expand Down Expand Up @@ -746,7 +759,6 @@ func (api *api) parseExpiresAt(expiresAtString string) (*time.Time, error) {
logger.Logger.WithField("expiresAt", expiresAtString).Error("Invalid expiresAt")
return nil, fmt.Errorf("invalid expiresAt: %v", err)
}
expiresAtValue = time.Date(expiresAtValue.Year(), expiresAtValue.Month(), expiresAtValue.Day(), 23, 59, 59, 0, expiresAtValue.Location())
expiresAt = &expiresAtValue
}
return expiresAt, nil
Expand Down
3 changes: 3 additions & 0 deletions api/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ type App struct {
MaxAmountSat uint64 `json:"maxAmount"`
BudgetUsage uint64 `json:"budgetUsage"`
BudgetRenewal string `json:"budgetRenewal"`
Isolated bool `json:"isolated"`
Balance uint64 `json:"balance"`
}

type ListAppsResponse struct {
Expand All @@ -89,6 +91,7 @@ type CreateAppRequest struct {
ExpiresAt string `json:"expiresAt"`
Scopes []string `json:"scopes"`
ReturnTo string `json:"returnTo"`
Isolated bool `json:"isolated"`
}

type StartRequest struct {
Expand Down
2 changes: 1 addition & 1 deletion cmd/http/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,6 @@ func main() {
defer cancel()
e.Shutdown(ctx)
logger.Logger.Info("Echo server exited")
svc.WaitShutdown()
svc.Shutdown()
logger.Logger.Info("Service exited")
}
16 changes: 14 additions & 2 deletions db/db_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@ package db

import (
"encoding/hex"
"errors"
"fmt"
"slices"
"time"

"github.com/getAlby/hub/constants"
"github.com/getAlby/hub/events"
"github.com/getAlby/hub/logger"
"github.com/nbd-wtf/go-nostr"
Expand All @@ -23,7 +26,16 @@ func NewDBService(db *gorm.DB, eventPublisher events.EventPublisher) *dbService
}
}

func (svc *dbService) CreateApp(name string, pubkey string, maxAmountSat uint64, budgetRenewal string, expiresAt *time.Time, scopes []string) (*App, string, error) {
func (svc *dbService) CreateApp(name string, pubkey string, maxAmountSat uint64, budgetRenewal string, expiresAt *time.Time, scopes []string, isolated bool) (*App, string, error) {
if isolated && (slices.Contains(scopes, constants.GET_INFO_SCOPE)) {
// cannot return node info because the isolated app is a custodial subaccount
return nil, "", errors.New("Isolated app cannot have get_info scope")
}
if isolated && (slices.Contains(scopes, constants.SIGN_MESSAGE_SCOPE)) {
// cannot sign messages because the isolated app is a custodial subaccount
return nil, "", errors.New("Isolated app cannot have sign_message scope")
}

var pairingPublicKey string
var pairingSecretKey string
if pubkey == "" {
Expand All @@ -39,7 +51,7 @@ func (svc *dbService) CreateApp(name string, pubkey string, maxAmountSat uint64,
}
}

app := App{Name: name, NostrPubkey: pairingPublicKey}
app := App{Name: name, NostrPubkey: pairingPublicKey, Isolated: isolated}

err := svc.db.Transaction(func(tx *gorm.DB) error {
err := tx.Save(&app).Error
Expand Down
2 changes: 1 addition & 1 deletion db/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ type Transaction struct {
}

type DBService interface {
CreateApp(name string, pubkey string, maxAmountSat uint64, budgetRenewal string, expiresAt *time.Time, scopes []string) (*App, string, error)
CreateApp(name string, pubkey string, maxAmountSat uint64, budgetRenewal string, expiresAt *time.Time, scopes []string, isolated bool) (*App, string, error)
}

const (
Expand Down
3 changes: 3 additions & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
"@radix-ui/react-icons": "^1.3.0",
"@radix-ui/react-label": "^2.0.2",
"@radix-ui/react-navigation-menu": "^1.1.4",
"@radix-ui/react-popover": "^1.1.1",
"@radix-ui/react-progress": "^1.0.3",
"@radix-ui/react-select": "^2.0.0",
"@radix-ui/react-separator": "^1.0.3",
Expand All @@ -41,12 +42,14 @@
"canvas-confetti": "^1.9.2",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.0",
"date-fns": "^3.6.0",
"dayjs": "^1.11.10",
"embla-carousel-react": "^8.0.2",
"gradient-avatar": "^1.0.2",
"lucide-react": "^0.363.0",
"posthog-js": "^1.116.6",
"react": "^18.2.0",
"react-day-picker": "^8.10.1",
"react-dom": "^18.2.0",
"react-lottie": "^1.2.4",
"react-qr-code": "^2.0.12",
Expand Down
1 change: 1 addition & 0 deletions frontend/public/images/illustrations/alby-account-dark.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
81 changes: 62 additions & 19 deletions frontend/src/components/BudgetAmountSelect.tsx
Original file line number Diff line number Diff line change
@@ -1,31 +1,74 @@
import React from "react";
import { Input } from "src/components/ui/input";
import { Label } from "src/components/ui/label";
import { cn } from "src/lib/utils";
import { budgetOptions } from "src/types";

function BudgetAmountSelect({
value,
onChange,
}: {
value?: number;
value: number;
onChange: (value: number) => void;
}) {
const [customBudget, setCustomBudget] = React.useState(
value ? !Object.values(budgetOptions).includes(value) : false
);
return (
<div className="grid grid-cols-6 grid-rows-2 md:grid-rows-1 md:grid-cols-6 gap-2 text-xs">
{Object.keys(budgetOptions).map((budget) => {
const amount = budgetOptions[budget];
return (
<div
key={budget}
onClick={() => onChange(amount)}
className={`col-span-2 md:col-span-1 cursor-pointer rounded border-2 ${
value === amount ? "border-primary" : "border-muted"
} text-center py-4`}
>
{budget}
<br />
{amount ? "sats" : "#reckless"}
</div>
);
})}
</div>
<>
<div className="grid grid-cols-2 md:grid-cols-5 gap-2 text-xs mb-4">
{Object.keys(budgetOptions).map((budget) => {
return (
<div
key={budget}
onClick={() => {
setCustomBudget(false);
onChange(budgetOptions[budget]);
}}
className={cn(
"cursor-pointer rounded text-nowrap border-2 text-center p-4",
!customBudget && value == budgetOptions[budget]
? "border-primary"
: "border-muted"
)}
>
{`${budget} ${budgetOptions[budget] ? " sats" : ""}`}
</div>
);
})}
<div
onClick={() => {
setCustomBudget(true);
onChange(0);
}}
className={cn(
"cursor-pointer rounded border-2 text-center p-4 dark:text-white",
customBudget ? "border-primary" : "border-muted"
)}
>
Custom...
</div>
</div>
{customBudget && (
<div className="w-full mb-6">
<Label htmlFor="budget" className="block mb-2">
Custom budget amount (sats)
</Label>
<Input
id="budget"
name="budget"
type="number"
required
autoFocus
min={1}
value={value || ""}
onChange={(e) => {
onChange(parseInt(e.target.value));
}}
/>
</div>
)}
</>
);
}

Expand Down
43 changes: 29 additions & 14 deletions frontend/src/components/BudgetRenewalSelect.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { XIcon } from "lucide-react";
import React from "react";
import { Label } from "src/components/ui/label";
import {
Select,
SelectContent,
Expand All @@ -11,27 +13,40 @@ import { BudgetRenewalType, validBudgetRenewals } from "src/types";
interface BudgetRenewalProps {
value: BudgetRenewalType;
onChange: (value: BudgetRenewalType) => void;
disabled?: boolean;
onClose?: () => void;
}

const BudgetRenewalSelect: React.FC<BudgetRenewalProps> = ({
value,
onChange,
disabled,
onClose,
}) => {
return (
<Select value={value} onValueChange={onChange} disabled={disabled}>
<SelectTrigger className="w-[150px]">
<SelectValue placeholder={"placeholder"} />
</SelectTrigger>
<SelectContent>
{validBudgetRenewals.map((renewalOption) => (
<SelectItem key={renewalOption} value={renewalOption}>
{renewalOption.charAt(0).toUpperCase() + renewalOption.slice(1)}
</SelectItem>
))}
</SelectContent>
</Select>
<>
<Label htmlFor="budget-renewal" className="block mb-2">
Budget Renewal
</Label>
<div className="flex gap-2 items-center text-muted-foreground mb-4 text-sm">
<Select value={value} onValueChange={onChange}>
<SelectTrigger id="budget-renewal" className="w-[150px] capitalize">
<SelectValue placeholder={value} />
</SelectTrigger>
<SelectContent className="capitalize">
{validBudgetRenewals.map((renewalOption) => (
<SelectItem key={renewalOption} value={renewalOption}>
{renewalOption}
</SelectItem>
))}
</SelectContent>
{onClose && (
<XIcon
className="cursor-pointer w-4 text-muted-foreground"
onClick={onClose}
/>
)}
</Select>
</div>
</>
);
};

Expand Down
Loading
Loading